Source

elixir3 / elixir / relationships.py

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
'''
This module provides support for defining relationships between your Elixir
entities.  Elixir currently supports two syntaxes to do so: the default
`Attribute-based syntax`_ which supports the following types of relationships:
ManyToOne_, OneToMany_, OneToOne_ and ManyToMany_, as well as a
`DSL-based syntax`_ which provides the following statements: belongs_to_,
has_many_, has_one_ and has_and_belongs_to_many_.

======================
Attribute-based syntax
======================

The first argument to all these "normal" relationship classes is the name of
the class (entity) you are relating to.

Following that first mandatory argument, any number of additional keyword
arguments can be specified for advanced behavior. See each relationship type
for a list of their specific keyword arguments. At this point, we'll just note
that all the arguments that are not specifically processed by Elixir, as
mentioned in the documentation below are passed on to the SQLAlchemy
``relation`` function. So, please refer to the `SQLAlchemy relation function's
documentation <http://www.sqlalchemy.org/docs/05/reference/orm/mapping.html
#sqlalchemy.orm.relation>`_ for further detail about which
keyword arguments are supported.

You should keep in mind that the following
keyword arguments are automatically generated by Elixir and should not be used
unless you want to override the value provided by Elixir: ``uselist``,
``remote_side``, ``secondary``, ``primaryjoin`` and ``secondaryjoin``.

Additionally, if you want a bidirectionnal relationship, you should define the
inverse relationship on the other entity explicitly (as opposed to how
SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will
match relationships together automatically. If there are several relationships
of the same type between two entities, Elixir is not able to determine which
relationship is the inverse of which, so you have to disambiguate the
situation by giving the name of the inverse relationship in the ``inverse``
keyword argument.

Here is a detailed explanation of each relation type:

`ManyToOne`
-----------

Describes the child's side of a parent-child relationship.  For example,
a `Pet` object may belong to its owner, who is a `Person`.  This could be
expressed like so:

.. sourcecode:: python

    class Pet(Entity):
        owner = ManyToOne('Person')

Behind the scene, assuming the primary key of the `Person` entity is
an integer column named `id`, the ``ManyToOne`` relationship will
automatically add an integer column named `owner_id` to the entity, with a
foreign key referencing the `id` column of the `Person` entity.

In addition to the keyword arguments inherited from SQLAlchemy's relation
function, ``ManyToOne`` relationships accept the following optional arguments
which will be directed to the created column:

+----------------------+------------------------------------------------------+
| Option Name          | Description                                          |
+======================+======================================================+
| ``colname``          | Specify a custom name for the foreign key column(s). |
|                      | This argument accepts either a single string or a    |
|                      | list of strings. The number of strings passed must   |
|                      | match the number of primary key columns of the target|
|                      | entity. If this argument is not used, the name of the|
|                      | column(s) is generated with the pattern              |
|                      | defined in options.FKCOL_NAMEFORMAT, which is, by    |
|                      | default: "%(relname)s_%(key)s", where relname is the |
|                      | name of the ManyToOne relationship, and 'key' is the |
|                      | name (key) of the primary column in the target       |
|                      | entity. That's with, in the above Pet/owner example, |
|                      | the name of the column would be: "owner_id".         |
+----------------------+------------------------------------------------------+
| ``required``         | Specify whether or not this field can be set to None |
|                      | (left without a value). Defaults to ``False``,       |
|                      | unless the field is a primary key.                   |
+----------------------+------------------------------------------------------+
| ``primary_key``      | Specify whether or not the column(s) created by this |
|                      | relationship should act as a primary_key.            |
|                      | Defaults to ``False``.                               |
+----------------------+------------------------------------------------------+
| ``column_kwargs``    | A dictionary holding any other keyword argument you  |
|                      | might want to pass to the Column.                    |
+----------------------+------------------------------------------------------+
| ``target_column``    | Name (or list of names) of the target column(s).     |
|                      | If this argument is not specified, the target entity |
|                      | primary key column(s) are used.                      |
+----------------------+------------------------------------------------------+

The following optional arguments are also supported to customize the
ForeignKeyConstraint that is created:

+----------------------+------------------------------------------------------+
| Option Name          | Description                                          |
+======================+======================================================+
| ``use_alter``        | If True, SQLAlchemy will add the constraint in a     |
|                      | second SQL statement (as opposed to within the       |
|                      | create table statement). This permits to define      |
|                      | tables with a circular foreign key dependency        |
|                      | between them.                                        |
+----------------------+------------------------------------------------------+
| ``ondelete``         | Value for the foreign key constraint ondelete clause.|
|                      | May be one of: ``cascade``, ``restrict``,            |
|                      | ``set null``, or ``set default``.                    |
+----------------------+------------------------------------------------------+
| ``onupdate``         | Value for the foreign key constraint onupdate clause.|
|                      | May be one of: ``cascade``, ``restrict``,            |
|                      | ``set null``, or ``set default``.                    |
+----------------------+------------------------------------------------------+
| ``constraint_kwargs``| A dictionary holding any other keyword argument you  |
|                      | might want to pass to the Constraint.                |
+----------------------+------------------------------------------------------+

In some cases, you may want to declare the foreign key column explicitly,
instead of letting it be generated automatically.  There are several reasons to
that: it could be because you want to declare it with precise arguments and
using column_kwargs makes your code ugly, or because the name of
your column conflicts with the property name (in which case an error is
thrown).  In those cases, you can use the ``field`` argument to specify an
already-declared field to be used for the foreign key column.

For example, for the Pet example above, if you want the database column
(holding the foreign key) to be called 'owner', one should use the field
parameter to specify the field manually.

.. sourcecode:: python

    class Pet(Entity):
        owner_id = Field(Integer, colname='owner')
        owner = ManyToOne('Person', field=owner_id)

+----------------------+------------------------------------------------------+
| Option Name          | Description                                          |
+======================+======================================================+
| ``field``            | Specify the previously-declared field to be used for |
|                      | the foreign key column. Use of this parameter is     |
|                      | mutually exclusive with the colname and column_kwargs|
|                      | arguments.                                           |
+----------------------+------------------------------------------------------+


Additionally, Elixir supports the belongs_to_ statement as an alternative,
DSL-based, syntax to define ManyToOne_ relationships.


`OneToMany`
-----------

Describes the parent's side of a parent-child relationship when there can be
several children.  For example, a `Person` object has many children, each of
them being a `Person`. This could be expressed like so:

.. sourcecode:: python

    class Person(Entity):
        parent = ManyToOne('Person')
        children = OneToMany('Person')

Note that a ``OneToMany`` relationship **cannot exist** without a
corresponding ``ManyToOne`` relationship in the other way. This is because the
``OneToMany`` relationship needs the foreign key created by the ``ManyToOne``
relationship.

In addition to keyword arguments inherited from SQLAlchemy, ``OneToMany``
relationships accept the following optional (keyword) arguments:

+--------------------+--------------------------------------------------------+
| Option Name        | Description                                            |
+====================+========================================================+
| ``order_by``       | Specify which field(s) should be used to sort the      |
|                    | results given by accessing the relation field.         |
|                    | Note that this sort order is only applied when loading |
|                    | objects from the database. Objects appended to the     |
|                    | collection afterwards are not re-sorted in-memory on   |
|                    | the fly.                                               |
|                    | This argument accepts either a string or a list of     |
|                    | strings, each corresponding to the name of a field in  |
|                    | the target entity. These field names can optionally be |
|                    | prefixed by a minus (for descending order).            |
+--------------------+--------------------------------------------------------+
| ``filter``         | Specify a filter criterion (as a clause element) for   |
|                    | this relationship. This criterion will be ``and_`` ed  |
|                    | with the normal join criterion (primaryjoin) generated |
|                    | by Elixir for the relationship. For example:           |
|                    | boston_addresses =                                     |
|                    | OneToMany('Address', filter=Address.city == 'Boston')  |
+--------------------+--------------------------------------------------------+

Additionally, Elixir supports an alternate, DSL-based, syntax to define
OneToMany_ relationships, with the has_many_ statement.


`OneToOne`
----------

Describes the parent's side of a parent-child relationship when there is only
one child.  For example, a `Car` object has one gear stick, which is
represented as a `GearStick` object. This could be expressed like so:

.. sourcecode:: python

    class Car(Entity):
        gear_stick = OneToOne('GearStick', inverse='car')

    class GearStick(Entity):
        car = ManyToOne('Car')

Note that a ``OneToOne`` relationship **cannot exist** without a corresponding
``ManyToOne`` relationship in the other way. This is because the ``OneToOne``
relationship needs the foreign_key created by the ``ManyToOne`` relationship.

Additionally, Elixir supports an alternate, DSL-based, syntax to define
OneToOne_ relationships, with the has_one_ statement.


`ManyToMany`
------------

Describes a relationship in which one kind of entity can be related to several
objects of the other kind but the objects of that other kind can be related to
several objects of the first kind.  For example, an `Article` can have several
tags, but the same `Tag` can be used on several articles.

.. sourcecode:: python

    class Article(Entity):
        tags = ManyToMany('Tag')

    class Tag(Entity):
        articles = ManyToMany('Article')

Behind the scene, the ``ManyToMany`` relationship will automatically create an
intermediate table to host its data.

Note that you don't necessarily need to define the inverse relationship.  In
our example, even though we want tags to be usable on several articles, we
might not be interested in which articles correspond to a particular tag.  In
that case, we could have omitted the `Tag` side of the relationship.

If your ``ManyToMany`` relationship is self-referencial, the entity
containing it is autoloaded (and you don't intend to specify both the
primaryjoin and secondaryjoin arguments manually), you must specify at least
one of either the ``remote_colname`` or ``local_colname`` argument.

In addition to keyword arguments inherited from SQLAlchemy, ``ManyToMany``
relationships accept the following optional (keyword) arguments:

+--------------------+--------------------------------------------------------+
| Option Name        | Description                                            |
+====================+========================================================+
| ``tablename``      | Specify a custom name for the intermediary table. This |
|                    | can be used both when the tables needs to be created   |
|                    | and when the table is autoloaded/reflected from the    |
|                    | database. If this argument is not used, a name will be |
|                    | automatically generated by Elixir depending on the name|
|                    | of the tables of the two entities of the relationship, |
|                    | the name of the relationship, and, if present, the name|
|                    | of its inverse. Even though this argument is optional, |
|                    | it is wise to use it if you are not sure what are the  |
|                    | exact consequence of using a generated table name.     |
+--------------------+--------------------------------------------------------+
| ``schema``         | Specify a custom schema for the intermediate table.    |
|                    | This can be used both when the tables needs to         |
|                    | be created and when the table is autoloaded/reflected  |
|                    | from the database.                                     |
+--------------------+--------------------------------------------------------+
| ``remote_colname`` | A string or list of strings specifying the names of    |
|                    | the column(s) in the intermediary table which          |
|                    | reference the "remote"/target entity's table.          |
+--------------------+--------------------------------------------------------+
| ``local_colname``  | A string or list of strings specifying the names of    |
|                    | the column(s) in the intermediary table which          |
|                    | reference the "local"/current entity's table.          |
+--------------------+--------------------------------------------------------+
| ``table``          | Use a manually created table. If this argument is      |
|                    | used, Elixir will not generate a table for this        |
|                    | relationship, and use the one given instead. This      |
|                    | argument only accepts SQLAlchemy's Table objects.      |
+--------------------+--------------------------------------------------------+
| ``order_by``       | Specify which field(s) should be used to sort the      |
|                    | results given by accessing the relation field.         |
|                    | Note that this sort order is only applied when loading |
|                    | objects from the database. Objects appended to the     |
|                    | collection afterwards are not re-sorted in-memory on   |
|                    | the fly.                                               |
|                    | This argument accepts either a string or a list of     |
|                    | strings, each corresponding to the name of a field in  |
|                    | the target entity. These field names can optionally be |
|                    | prefixed by a minus (for descending order).            |
+----------------------+------------------------------------------------------+
| ``ondelete``       | Value for the foreign key constraint ondelete clause.  |
|                    | May be one of: ``cascade``, ``restrict``,              |
|                    | ``set null``, or ``set default``.                      |
+--------------------+--------------------------------------------------------+
| ``onupdate``       | Value for the foreign key constraint onupdate clause.  |
|                    | May be one of: ``cascade``, ``restrict``,              |
|                    | ``set null``, or ``set default``.                      |
+--------------------+--------------------------------------------------------+
| ``table_kwargs``   | A dictionary holding any other keyword argument you    |
|                    | might want to pass to the underlying Table object.     |
+--------------------+--------------------------------------------------------+


================
DSL-based syntax
================

The following DSL statements provide an alternative way to define relationships
between your entities. The first argument to all those statements is the name
of the relationship, the second is the 'kind' of object you are relating to
(it is usually given using the ``of_kind`` keyword).

`belongs_to`
------------

The ``belongs_to`` statement is the DSL syntax equivalent to the ManyToOne_
relationship. As such, it supports all the same arguments as ManyToOne_
relationships.

.. sourcecode:: python

    class Pet(Entity):
        belongs_to('feeder', of_kind='Person')
        belongs_to('owner', of_kind='Person', colname="owner_id")


`has_many`
----------

The ``has_many`` statement is the DSL syntax equivalent to the OneToMany_
relationship. As such, it supports all the same arguments as OneToMany_
relationships.

.. sourcecode:: python

    class Person(Entity):
        belongs_to('parent', of_kind='Person')
        has_many('children', of_kind='Person')

There is also an alternate form of the ``has_many`` relationship that takes
only two keyword arguments: ``through`` and ``via`` in order to encourage a
richer form of many-to-many relationship that is an alternative to the
``has_and_belongs_to_many`` statement.  Here is an example:

.. sourcecode:: python

    class Person(Entity):
        has_field('name', Unicode)
        has_many('assignments', of_kind='Assignment')
        has_many('projects', through='assignments', via='project')

    class Assignment(Entity):
        has_field('start_date', DateTime)
        belongs_to('person', of_kind='Person')
        belongs_to('project', of_kind='Project')

    class Project(Entity):
        has_field('title', Unicode)
        has_many('assignments', of_kind='Assignment')

In the above example, a `Person` has many `projects` through the `Assignment`
relationship object, via a `project` attribute.


`has_one`
---------

The ``has_one`` statement is the DSL syntax equivalent to the OneToOne_
relationship. As such, it supports all the same arguments as OneToOne_
relationships.

.. sourcecode:: python

    class Car(Entity):
        has_one('gear_stick', of_kind='GearStick', inverse='car')

    class GearStick(Entity):
        belongs_to('car', of_kind='Car')


`has_and_belongs_to_many`
-------------------------

The ``has_and_belongs_to_many`` statement is the DSL syntax equivalent to the
ManyToMany_ relationship. As such, it supports all the same arguments as
ManyToMany_ relationships.

.. sourcecode:: python

    class Article(Entity):
        has_and_belongs_to_many('tags', of_kind='Tag')

    class Tag(Entity):
        has_and_belongs_to_many('articles', of_kind='Article')

'''

import warnings

from sqlalchemy import ForeignKeyConstraint, Column, Table, and_
from sqlalchemy.orm import relation, backref, class_mapper
from sqlalchemy.ext.associationproxy import association_proxy

import options
from elixir.statements import ClassMutator
from elixir.properties import Property
from elixir.entity import EntityMeta, DEBUG

__doc_all__ = []


class Relationship(Property):
    '''
    Base class for relationships.
    '''

    def __init__(self, of_kind, inverse=None, *args, **kwargs):
        super(Relationship, self).__init__()

        self.of_kind = of_kind
        self.inverse_name = inverse

        self._target = None

        self.property = None # sqlalchemy property
        self.backref = None  # sqlalchemy backref

        #TODO: unused for now
        self.args = args
        self.kwargs = kwargs

    def attach(self, entity, name):
        super(Relationship, self).attach(entity, name)
        entity._descriptor.relationships.append(self)

    def create_pk_cols(self):
        self.create_keys(True)

    def create_non_pk_cols(self):
        self.create_keys(False)

    def create_keys(self, pk):
        '''
        Subclasses (ie. concrete relationships) may override this method to
        create foreign keys.
        '''

    def create_properties(self):
        if self.property or self.backref:
            return

        kwargs = self.get_prop_kwargs()
        if 'order_by' in kwargs:
            kwargs['order_by'] = \
                self.target._descriptor.translate_order_by(kwargs['order_by'])

        # transform callable arguments
        for arg in ('primaryjoin', 'secondaryjoin', 'remote_side',
                    'foreign_keys'):
            kwarg = kwargs.get(arg, None)
            if hasattr(kwarg, '__call__'):
                kwargs[arg] = kwarg()

        # viewonly relationships need to create "standalone" relations (ie
        # shouldn't be a backref of another relation).
        if self.inverse and not kwargs.get('viewonly', False):
            # check if the inverse was already processed (and thus has already
            # defined a backref we can use)
            if self.inverse.backref:
                # let the user override the backref argument
                if 'backref' not in kwargs:
                    kwargs['backref'] = self.inverse.backref
            else:
                # SQLAlchemy doesn't like when 'secondary' is both defined on
                # the relation and the backref
                kwargs.pop('secondary', None)

                # define backref for use by the inverse
                self.backref = backref(self.name, **kwargs)
                return

        self.property = relation(self.target, **kwargs)
        self.add_mapper_property(self.name, self.property)

    @property
    def target(self):
        if not self._target:
            if isinstance(self.of_kind, basestring):
                collection = self.entity._descriptor.collection
                self._target = collection.resolve(self.of_kind, self.entity)
            else:
                self._target = self.of_kind
        return self._target

    @property
    def inverse(self):
        if not hasattr(self, '_inverse'):
            if self.inverse_name:
                desc = self.target._descriptor
                inverse = desc.find_relationship(self.inverse_name)
                if inverse is None:
                    raise Exception(
                              "Couldn't find a relationship named '%s' in "
                              "entity '%s' or its parent entities."
                              % (self.inverse_name, self.target.__name__))
                assert self.match_type_of(inverse), \
                    "Relationships '%s' in entity '%s' and '%s' in entity " \
                    "'%s' cannot be inverse of each other because their " \
                    "types do not form a valid combination." % \
                    (self.name, self.entity.__name__,
                     self.inverse_name, self.target.__name__)
            else:
                check_reverse = not self.kwargs.get('viewonly', False)
                if isinstance(self.target, EntityMeta):
                    inverse = self.target._descriptor.get_inverse_relation(
                        self, check_reverse=check_reverse)
                else:
                    inverse = None
            self._inverse = inverse
            if inverse and not self.kwargs.get('viewonly', False):
                inverse._inverse = self

        return self._inverse

    def match_type_of(self, other):
        return False

    def is_inverse(self, other):
        # viewonly relationships are not symmetrical: a viewonly relationship
        # should have exactly one inverse (a ManyToOne relationship), but that
        # inverse shouldn't have the viewonly relationship as its inverse.
        return not other.kwargs.get('viewonly', False) and \
               other is not self and \
               self.match_type_of(other) and \
               self.entity == other.target and \
               other.entity == self.target and \
               (self.inverse_name == other.name or not self.inverse_name) and \
               (other.inverse_name == self.name or not other.inverse_name)


class ManyToOne(Relationship):
    '''

    '''

    def __init__(self, of_kind,
                 column_kwargs=None,
                 colname=None, required=None, primary_key=None,
                 field=None,
                 constraint_kwargs=None,
                 use_alter=None, ondelete=None, onupdate=None,
                 target_column=None,
                 *args, **kwargs):

        # 1) handle column-related args

        # check that the column arguments don't conflict
        assert not (field and (column_kwargs or colname)), \
               "ManyToOne can accept the 'field' argument or column " \
               "arguments ('colname' or 'column_kwargs') but not both!"

        if colname and not isinstance(colname, list):
            colname = [colname]
        self.colname = colname or []

        column_kwargs = column_kwargs or {}
        # kwargs go by default to the relation(), so we need to manually
        # extract those targeting the Column
        if required is not None:
            column_kwargs['nullable'] = not required
        if primary_key is not None:
            column_kwargs['primary_key'] = primary_key
        # by default, created columns will have an index.
        column_kwargs.setdefault('index', True)
        self.column_kwargs = column_kwargs

        if field and not isinstance(field, list):
            field = [field]
        self.field = field or []

        # 2) handle constraint kwargs
        constraint_kwargs = constraint_kwargs or {}
        if use_alter is not None:
            constraint_kwargs['use_alter'] = use_alter
        if ondelete is not None:
            constraint_kwargs['ondelete'] = ondelete
        if onupdate is not None:
            constraint_kwargs['onupdate'] = onupdate
        self.constraint_kwargs = constraint_kwargs

        # 3) misc arguments
        if target_column and not isinstance(target_column, list):
            target_column = [target_column]
        self.target_column = target_column

        self.foreign_key = []
        self.primaryjoin_clauses = []

        super(ManyToOne, self).__init__(of_kind, *args, **kwargs)

    def match_type_of(self, other):
        return isinstance(other, (OneToMany, OneToOne))

    @property
    def target_table(self):
        if isinstance(self.target, EntityMeta):
            return self.target._descriptor.table
        else:
            return class_mapper(self.target).local_table

    def create_keys(self, pk):
        '''
        Find all primary keys on the target and create foreign keys on the
        source accordingly.
        '''

        if self.foreign_key:
            return

        if self.column_kwargs.get('primary_key', False) != pk:
            return

        source_desc = self.entity._descriptor
        if isinstance(self.target, EntityMeta):
            # make sure the target has all its pk set up
            #FIXME: this is not enough when specifying target_column manually,
            # on unique, non-pk col, see tests/test_m2o.py:test_non_pk_forward
            self.target._descriptor.create_pk_cols()

        #XXX: another option, instead of the FakeTable, would be to create an
        # EntityDescriptor for the SA class.
        target_table = self.target_table

        if source_desc.autoload:
            #TODO: allow target_column to be used as an alternative to
            # specifying primaryjoin, to be consistent with non-autoloaded
            # tables
            if self.colname:
                if 'primaryjoin' not in self.kwargs:
                    self.primaryjoin_clauses = \
                        _get_join_clauses(self.entity.table,
                                          self.colname, None,
                                          target_table)[0]
                    if not self.primaryjoin_clauses:
                        colnames = ', '.join(self.colname)
                        raise Exception(
                            "Couldn't find a foreign key constraint in table "
                            "'%s' using the following columns: %s."
                            % (self.entity.table.name, colnames))
            else:
                # in this case we let SA handle everything. 
                # XXX: we might want to try to build join clauses anyway so 
                # that we know whether there is an ambiguity or not, and
                # suggest using colname if there is one
                pass
            if self.field:
                raise NotImplementedError(
                    "'field' argument not allowed on autoloaded table "
                    "relationships.")
        else:
            fk_refcols = []
            fk_colnames = []

            if self.target_column is None:
                target_columns = target_table.primary_key.columns
            else:
                target_columns = [target_table.columns[col]
                                  for col in self.target_column]

            if not target_columns:
                raise Exception("No primary key found in target table ('%s') "
                                "for the '%s' relationship of the '%s' entity."
                                % (target_table.name, self.name,
                                   self.entity.__name__))
            if self.colname and \
               len(self.colname) != len(target_columns):
                raise Exception(
                        "The number of column names provided in the colname "
                        "keyword argument of the '%s' relationship of the "
                        "'%s' entity is not the same as the number of columns "
                        "of the primary key of '%s'."
                        % (self.name, self.entity.__name__,
                           self.target.__name__))

            for key_num, target_col in enumerate(target_columns):
                if self.field:
                    col = self.field[key_num].column
                else:
                    if self.colname:
                        colname = self.colname[key_num]
                    else:
                        colname = options.FKCOL_NAMEFORMAT % \
                                  {'relname': self.name,
                                   'key': target_col.key}

                    # We can't add the column to the table directly as the
                    # table might not be created yet.
                    col = Column(colname, target_col.type,
                                 **self.column_kwargs)
                    source_desc.add_column(col)

                    # If the column name was specified, and it is the same as
                    # this property's name, there is going to be a conflict.
                    # Don't allow this to happen.
                    if col.key == self.name:
                        raise ValueError(
                                 "ManyToOne named '%s' in '%s' conficts "
                                 " with the column of the same name. "
                                 "You should probably define the foreign key "
                                 "field manually and use the 'field' "
                                 "argument on the ManyToOne relationship"
                                 % (self.name, self.entity.__name__))

                # Build the list of local columns which will be part of
                # the foreign key
                self.foreign_key.append(col)

                # Store the names of those columns
                fk_colnames.append(col.key)

                # Build the list of column "paths" the foreign key will
                # point to
                fk_refcols.append("%s.%s" % \
                                  (target_table.fullname, target_col.key))

                # Build up the primary join. This is needed when you have
                # several ManyToOne relationships between two objects
                self.primaryjoin_clauses.append(col == target_col)

            if 'name' not in self.constraint_kwargs:
                # In some databases (at least MySQL) the constraint name needs
                # to be unique for the whole database, instead of per table.
                fk_name = options.CONSTRAINT_NAMEFORMAT % \
                          {'tablename': source_desc.tablename,
                           'colnames': '_'.join(fk_colnames)}
                self.constraint_kwargs['name'] = fk_name

            source_desc.add_constraint(
                ForeignKeyConstraint(fk_colnames, fk_refcols,
                                     **self.constraint_kwargs))

    def get_prop_kwargs(self):
        kwargs = {'uselist': False}

        if self.entity.table is self.target_table:
            # this is needed because otherwise SA has no way to know what is
            # the direction of the relationship since both columns present in
            # the primaryjoin belong to the same table. In other words, it is
            # necessary to know if this particular relation
            # is the many-to-one side, or the one-to-xxx side. The foreignkey
            # doesn't help in this case.
            kwargs['remote_side'] = \
                [col for col in self.target_table.primary_key.columns]

        if self.primaryjoin_clauses:
            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)

        kwargs.update(self.kwargs)

        return kwargs


class OneToOne(Relationship):
    uselist = False

    def __init__(self, of_kind, filter=None, *args, **kwargs):
        self.filter = filter
        if filter is not None:
            # We set viewonly to True by default for filtered relationships,
            # unless manually overridden.
            # This is not strictly necessary, as SQLAlchemy allows non viewonly
            # relationships with a custom join/filter. The example at:
            # SADOCS/05/mappers.html#advdatamapping_relation_customjoin
            # is not viewonly. Those relationships can be used as if the extra
            # filter wasn't present when inserting. This can lead to a
            # confusing behavior (if you insert data which doesn't match the
            # extra criterion it'll get inserted anyway but you won't see it
            # when you query back the attribute after a round-trip to the
            # database).
            if 'viewonly' not in kwargs:
                kwargs['viewonly'] = True
        super(OneToOne, self).__init__(of_kind, *args, **kwargs)

    def match_type_of(self, other):
        return isinstance(other, ManyToOne)

    def create_keys(self, pk):
        # make sure an inverse relationship exists
        if self.inverse is None:
            raise Exception(
                      "Couldn't find any relationship in '%s' which "
                      "match as inverse of the '%s' relationship "
                      "defined in the '%s' entity. If you are using "
                      "inheritance you "
                      "might need to specify inverse relationships "
                      "manually by using the 'inverse' argument."
                      % (self.target, self.name,
                         self.entity))

    def get_prop_kwargs(self):
        kwargs = {'uselist': self.uselist}

        #TODO: for now, we don't break any test if we remove those 2 lines.
        # So, we should either complete the selfref test to prove that they
        # are indeed useful, or remove them. It might be they are indeed
        # useless because the remote_side is already setup in the other way
        # (ManyToOne).
        if self.entity.table is self.target.table:
            # When using a manual/autoloaded table, it will be assigned
            # an empty list, which doesn't seem to upset SQLAlchemy
            kwargs['remote_side'] = self.inverse.foreign_key

        # Contrary to ManyToMany relationships, we need to specify the join
        # clauses even if this relationship is not self-referencial because
        # there could be several ManyToOne from the target class to us.
        joinclauses = self.inverse.primaryjoin_clauses
        if self.filter:
            # We need to make a copy of the joinclauses, to not add the filter
            # on the backref
            joinclauses = joinclauses[:] + [self.filter(self.target.table.c)]
        if joinclauses:
            kwargs['primaryjoin'] = and_(*joinclauses)

        kwargs.update(self.kwargs)

        return kwargs


class OneToMany(OneToOne):
    uselist = True


class ManyToMany(Relationship):
    uselist = True

    def __init__(self, of_kind, tablename=None,
                 local_colname=None, remote_colname=None,
                 ondelete=None, onupdate=None,
                 table=None, schema=None,
                 filter=None,
                 table_kwargs=None,
                 *args, **kwargs):
        self.user_tablename = tablename

        if local_colname and not isinstance(local_colname, list):
            local_colname = [local_colname]
        self.local_colname = local_colname or []
        if remote_colname and not isinstance(remote_colname, list):
            remote_colname = [remote_colname]
        self.remote_colname = remote_colname or []

        self.ondelete = ondelete
        self.onupdate = onupdate

        self.table = table
        self.schema = schema

        #TODO: this can probably be simplified/moved elsewhere since the
        #argument disappeared
        self.column_format = options.M2MCOL_NAMEFORMAT
        if not hasattr(self.column_format, '__call__'):
            # we need to store the format in a variable so that the
            # closure of the lambda is correct
            format = self.column_format
            self.column_format = lambda data: format % data
        if options.MIGRATION_TO_07_AID:
            self.column_format = \
                migration_aid_m2m_column_formatter(
                    lambda data: options.OLD_M2MCOL_NAMEFORMAT % data,
                    self.column_format)

        self.filter = filter
        if filter is not None:
            # We set viewonly to True by default for filtered relationships,
            # unless manually overridden.
            if 'viewonly' not in kwargs:
                kwargs['viewonly'] = True

        self.table_kwargs = table_kwargs or {}

        self.primaryjoin_clauses = []
        self.secondaryjoin_clauses = []

        super(ManyToMany, self).__init__(of_kind, *args, **kwargs)

    def match_type_of(self, other):
        return isinstance(other, ManyToMany)

    def create_tables(self):
        if self.table is not None:
            if 'primaryjoin' not in self.kwargs or \
               'secondaryjoin' not in self.kwargs:
                self._build_join_clauses()
            assert self.inverse is None or self.inverse.table is None or \
                   self.inverse.table is self.table
            return

        if self.inverse:
            inverse = self.inverse
            if inverse.table is not None:
                self.table = inverse.table
                self.primaryjoin_clauses = inverse.secondaryjoin_clauses
                self.secondaryjoin_clauses = inverse.primaryjoin_clauses
                return

            assert not inverse.user_tablename or not self.user_tablename or \
                   inverse.user_tablename == self.user_tablename
            assert not inverse.remote_colname or not self.local_colname or \
                   inverse.remote_colname == self.local_colname
            assert not inverse.local_colname or not self.remote_colname or \
                   inverse.local_colname == self.remote_colname
            assert not inverse.schema or not self.schema or \
                   inverse.schema == self.schema
            assert not inverse.table_kwargs or not self.table_kwargs or \
                   inverse.table_kwargs == self.table_kwargs

            self.user_tablename = inverse.user_tablename or self.user_tablename
            self.local_colname = inverse.remote_colname or self.local_colname
            self.remote_colname = inverse.local_colname or self.remote_colname
            self.schema = inverse.schema or self.schema
            self.local_colname = inverse.remote_colname or self.local_colname

        # compute table_kwargs
        complete_kwargs = options.options_defaults['table_options'].copy()
        complete_kwargs.update(self.table_kwargs)

        #needs: table_options['schema'], autoload, tablename, primary_keys,
        #entity.__name__, table_fullname
        e1_desc = self.entity._descriptor
        e2_desc = self.target._descriptor

        e1_schema = e1_desc.table_options.get('schema', None)
        e2_schema = e2_desc.table_options.get('schema', None)
        schema = (self.schema is not None) and self.schema or e1_schema

        assert e1_schema == e2_schema or self.schema, \
               "Schema %r for entity %s differs from schema %r of entity %s." \
               " Consider using the schema-parameter. "\
               % (e1_schema, self.entity.__name__,
                  e2_schema, self.target.__name__)

        # First, we compute the name of the table. Note that some of the
        # intermediary variables are reused later for the constraint
        # names.

        # We use the name of the relation for the first entity
        # (instead of the name of its primary key), so that we can
        # have two many-to-many relations between the same objects
        # without having a table name collision.
        source_part = "%s_%s" % (e1_desc.tablename, self.name)

        # And we use only the name of the table of the second entity
        # when there is no inverse, so that a many-to-many relation
        # can be defined without an inverse.
        if self.inverse:
            target_part = "%s_%s" % (e2_desc.tablename, self.inverse.name)
        else:
            target_part = e2_desc.tablename

        if self.user_tablename:
            tablename = self.user_tablename
        else:
            # We need to keep the table name consistent (independant of
            # whether this relation or its inverse is setup first).
            if self.inverse and source_part < target_part:
                #XXX: use a different scheme for selfref (to not include the
                #     table name twice)?
                tablename = "%s__%s" % (target_part, source_part)
            else:
                tablename = "%s__%s" % (source_part, target_part)

            if options.MIGRATION_TO_07_AID:
                oldname = (self.inverse and
                           e1_desc.tablename < e2_desc.tablename) and \
                          "%s__%s" % (target_part, source_part) or \
                          "%s__%s" % (source_part, target_part)
                if oldname != tablename:
                    warnings.warn(
                        "The generated table name for the '%s' relationship "
                        "on the '%s' entity changed from '%s' (the name "
                        "generated by Elixir 0.6.1 and earlier) to '%s'. "
                        "You should either rename the table in the database "
                        "to the new name or use the tablename argument on the "
                        "relationship to force the old name: tablename='%s'!"
                        % (self.name, self.entity.__name__, oldname,
                           tablename, oldname))

        if e1_desc.autoload:
            if not e2_desc.autoload:
                raise Exception(
                    "Entity '%s' is autoloaded and its '%s' "
                    "ManyToMany relationship points to "
                    "the '%s' entity which is not autoloaded"
                    % (self.entity.__name__, self.name,
                       self.target.__name__))

            self.table = Table(tablename, e1_desc.metadata, autoload=True,
                               **complete_kwargs)
            if 'primaryjoin' not in self.kwargs or \
               'secondaryjoin' not in self.kwargs:
                self._build_join_clauses()
        else:
            # We pre-compute the names of the foreign key constraints
            # pointing to the source (local) entity's table and to the
            # target's table

            # In some databases (at least MySQL) the constraint names need
            # to be unique for the whole database, instead of per table.
            source_fk_name = "%s_fk" % source_part
            if self.inverse:
                target_fk_name = "%s_fk" % target_part
            else:
                target_fk_name = "%s_inverse_fk" % source_part

            columns = []
            constraints = []

            for num, desc, fk_name, rel, inverse, colnames, join_clauses in (
              (0, e1_desc, source_fk_name, self, self.inverse,
               self.local_colname, self.primaryjoin_clauses),
              (1, e2_desc, target_fk_name, self.inverse, self,
               self.remote_colname, self.secondaryjoin_clauses)):

                fk_colnames = []
                fk_refcols = []
                if colnames:
                    assert len(colnames) == len(desc.primary_keys)
                else:
                    # The data generated here will be fed to the M2M column
                    # formatter to generate the name of the columns of the
                    # intermediate table for *one* side of the relationship,
                    # that is, from the intermediate table to the current
                    # entity, as stored in the "desc" variable.
                    data = {# A) relationships info

                            # the name of the rel going *from* the entity
                            # we are currently generating a column pointing
                            # *to*. This is generally *not* what you want to
                            # use. eg in a "Post" and "Tag" example, with
                            # relationships named 'tags' and 'posts', when
                            # creating the columns from the intermediate
                            # table to the "Post" entity, 'relname' will
                            # contain 'tags'.
                            'relname': rel and rel.name or 'inverse',

                            # the name of the inverse relationship. In the
                            # above example, 'inversename' will contain
                            # 'posts'.
                            'inversename': inverse and inverse.name
                                                   or 'inverse',
                            # is A == B?
                            'selfref': e1_desc is e2_desc,
                            # provided for backward compatibility, DO NOT USE!
                            'num': num,
                            # provided for backward compatibility, DO NOT USE!
                            'numifself': e1_desc is e2_desc and str(num + 1)
                                                            or '',
                            # B) target information (from the perspective of
                            #    the intermediate table)
                            'target': desc.entity,
                            'entity': desc.entity.__name__.lower(),
                            'tablename': desc.tablename,

                            # C) current (intermediate) table name
                            'current_table': tablename
                           }
                    colnames = []
                    for pk_col in desc.primary_keys:
                        data.update(key=pk_col.key)
                        colnames.append(self.column_format(data))

                for pk_col, colname in zip(desc.primary_keys, colnames):
                    col = Column(colname, pk_col.type, primary_key=True)
                    columns.append(col)

                    # Build the list of local columns which will be part
                    # of the foreign key.
                    fk_colnames.append(colname)

                    # Build the list of column "paths" the foreign key will
                    # point to
                    target_path = "%s.%s" % (desc.table_fullname, pk_col.key)
                    fk_refcols.append(target_path)

                    # Build join clauses (in case we have a self-ref)
                    if self.entity is self.target:
                        join_clauses.append(col == pk_col)

                onupdate = rel and rel.onupdate
                ondelete = rel and rel.ondelete

                #FIXME: fk_name is misleading
                constraints.append(
                    ForeignKeyConstraint(fk_colnames, fk_refcols,
                                         name=fk_name, onupdate=onupdate,
                                         ondelete=ondelete))

            args = columns + constraints

            self.table = Table(tablename, e1_desc.metadata,
                               schema=schema, *args, **complete_kwargs)
            if DEBUG:
                print self.table.repr2()

    def _build_join_clauses(self):
        # In the case we have a self-reference, we need to build join clauses
        if self.entity is self.target:
            if not self.local_colname and not self.remote_colname:
                raise Exception(
                    "Self-referential ManyToMany "
                    "relationships in autoloaded entities need to have at "
                    "least one of either 'local_colname' or 'remote_colname' "
                    "argument specified. The '%s' relationship in the '%s' "
                    "entity doesn't have either."
                    % (self.name, self.entity.__name__))

            self.primaryjoin_clauses, self.secondaryjoin_clauses = \
                _get_join_clauses(self.table,
                                  self.local_colname, self.remote_colname,
                                  self.entity.table)

    def get_prop_kwargs(self):
        kwargs = {'secondary': self.table,
                  'uselist': self.uselist}

        if self.filter:
            # we need to make a copy of the joinclauses
            secondaryjoin_clauses = self.secondaryjoin_clauses[:] + \
                                    [self.filter(self.target.table.c)]
        else:
            secondaryjoin_clauses = self.secondaryjoin_clauses

        if self.target is self.entity or self.filter:
            kwargs['primaryjoin'] = and_(*self.primaryjoin_clauses)
            kwargs['secondaryjoin'] = and_(*secondaryjoin_clauses)

        kwargs.update(self.kwargs)

        return kwargs

    def is_inverse(self, other):
        return super(ManyToMany, self).is_inverse(other) and \
               (self.user_tablename == other.user_tablename or
                (not self.user_tablename and not other.user_tablename))


def migration_aid_m2m_column_formatter(oldformatter, newformatter):
    def debug_formatter(data):
        old_name = oldformatter(data)
        new_name = newformatter(data)
        if new_name != old_name:
            complete_data = data.copy()
            complete_data.update(old_name=old_name,
                                 new_name=new_name,
                                 targetname=data['target'].__name__)
            # Specifying a stacklevel is useless in this case as the name
            # generation is triggered by setup_all(), not by the declaration
            # of the offending relationship.
            warnings.warn("The '%(old_name)s' column in the "
                          "'%(current_table)s' table, used as the "
                          "intermediate table for the '%(relname)s' "
                          "relationship on the '%(targetname)s' entity "
                          "was renamed to '%(new_name)s'."
                          % complete_data)
        return new_name
    return debug_formatter


def _get_join_clauses(local_table, local_cols1, local_cols2, target_table):
    primary_join, secondary_join = [], []
    cols1 = local_cols1[:]
    cols1.sort()
    cols1 = tuple(cols1)

    if local_cols2 is not None:
        cols2 = local_cols2[:]
        cols2.sort()
        cols2 = tuple(cols2)
    else:
        cols2 = None

    # Build a map of fk constraints pointing to the correct table.
    # The map is indexed on the local col names.
    constraint_map = {}
    for constraint in local_table.constraints:
        if isinstance(constraint, ForeignKeyConstraint):
            use_constraint = True
            fk_colnames = []

            # if all columns point to the correct table, we use the constraint
            #TODO: check that it contains as many columns as the pk of the
            #target entity, or even that it points to the actual pk columns
            for fk in constraint.elements:
                if fk.references(target_table):
                    # local column key
                    fk_colnames.append(fk.parent.key)
                else:
                    use_constraint = False
            if use_constraint:
                fk_colnames.sort()
                constraint_map[tuple(fk_colnames)] = constraint

    # Either the fk column names match explicitely with the columns given for
    # one of the joins (primary or secondary), or we assume the current
    # columns match because the columns for this join were not given and we
    # know the other join is either not used (is None) or has an explicit
    # match.

#TODO: rewrite this. Even with the comment, I don't even understand it myself.
    for cols, constraint in constraint_map.iteritems():
        if cols == cols1 or (cols != cols2 and
                             not cols1 and (cols2 in constraint_map or
                                            cols2 is None)):
            join = primary_join
        elif cols == cols2 or (cols2 == () and cols1 in constraint_map):
            join = secondary_join
        else:
            continue
        for fk in constraint.elements:
            join.append(fk.parent == fk.column)
    return primary_join, secondary_join


def rel_mutator_handler(target):
    def handler(entity, name, of_kind=None, through=None, via=None,
                *args, **kwargs):
        if through and via:
            setattr(entity, name,
                    association_proxy(through, via, **kwargs))
            return
        elif through or via:
            raise Exception("'through' and 'via' relationship keyword "
                            "arguments should be used in combination.")
        rel = target(of_kind, *args, **kwargs)
        rel.attach(entity, name)
    return handler


belongs_to = ClassMutator(rel_mutator_handler(ManyToOne))
has_one = ClassMutator(rel_mutator_handler(OneToOne))
has_many = ClassMutator(rel_mutator_handler(OneToMany))
has_and_belongs_to_many = ClassMutator(rel_mutator_handler(ManyToMany))