On Thu, 2003-05-29 at 23:33, Edmund Lian wrote:
> Ian,
>
> I've been banging away at SQLObject all day, trying to jam a small
> portion of my data model into it, or else rewrite it so it would fit.
> Here are my thoughts so far, each worth $0.02, and in no particular order.
>
> I have appended + and minus signs to indicate strength of feeling. More
> of either = stronger positive or negative reation. +- = no reaction.
>
> 1. Being able to autogenerate schema is nice, and this is how I've been
> using testing SQLObject (SO). ++
Yes, very useful for testing, moreso than I expected. Great for unit
testing.
> 2. Support for multiple DBs, particularly PostgreSQL, is good. +++
>
> 3. There needs to be an object.delete() method that does not require you
> to supply the object ID. When not supplied, it deletes the current
> object. There is a bug in this right now. When you do
> object.destroySelf(), the corresponding row is deleted from the DB, but
> the instance data is not invalidated. +-
Invalidating instance data is annoying. destroySelf is annoying. It
all reeks of manual memory management, though I suppose such is life
with an RDBMS.
> 4. Being able to supply a callable object for the default value of a
> column is good. +++
>
> 5. Being able to override table name is important because when you have
> lots of tables floating around, you often want to control their names so
> that you group them together is a specific way when the table names are
> sorted. ++++
>
> 6. Need to have some way of specifying unique constraints for single and
> groups of columns for auto schema generation. --- See (7) below.
At some point SQLObject shouldn't do every detail of schema creation.
Maybe there should be a hook for adding your own SQL that gets executed
on createTable/dropTable, so you can use those methods without (or in
addition to) the automatic table definition.
> 7. I don't use enums at all. For table driven applications, I use
> primary keys coupled with foreign key constraints. This allows you to
> change the allowed values of a column by varying the contents of another
> table. This is one argument against using synthetic primary keys. But
> using synthetic primary keys is still OK so long as unique columns can
> be specified, and the foreign key constraint can take the name of a column.
>
> 8. There is no way to specify (at least not that I can see), outer
> joins. These are important. ---
You'll have to give an example of what you want to do. I don't see the
need.
> 9. Method of specifying joins seems a tad clumsy. Object Relational
> Membrane's method is cleaner.
Yes, needing to declare both forward and backward references is a tad
awkward. There may yet be advantages of joins being in both locations
-- for instance, being able to add ordering information. But eh... it's
not perfect.
Though when I look at ORM, it's not that much different than what I'm
doing, and I even avoid the circular dependency problem. The names may
be better in ORM -- I don't like my names. But then one2many -- who is
the one, who is the many? That's what I find hard in naming.
> 10. There needs to be a way to specify a not null constraint.
notNull=True or notNone=True as a keyword argument to your column.
> 11. As an extension of (7), it is clumsy to have to pass in an object
> (A) when creating another object (B) that has a foreign key constraint
> refering to a column in (A). Here's an example that assumes I can add
> unique (6), not null constraints (10), and name a column for a foreign
> key (7) to a column:
>
> class Gender(SQLObject):
> _columns = [
> StringCol("name", unique=1, nullable=0)]
StringCol("name", alternateID=True)
# or with the new syntax:
name = StringCol(alternateID=True)
male = Gender.byName('male')
> class Person(SQLObject):
> _columns = [
> StringCol("name"),
> StringCol("gender", nullable=0, foreignKey="Gender.name")]
Hmmm... that's not so bad. But it does complicate the SQL a fair
amount, at least if it wasn't going to involve two SQL queries for doing
person.gender.
> With this schema, one should be able to do:
>
> Gender.new(name="Male") # Done once
> Gender.new(name="Female") # Done once
> man = Person(name="Geoff", gender="Male") # somewhere else in the code
This, though... I dunno. I'm not sure about it at all.
> If one has to pass in an instance of Gender, then it becomes very clumsy
> and slow, because you have to instantiate the object you want first. i.e.:
>
> male = Gender.select(Gender.q.name=="Male")
> man = Person(name="Geoff", gender=male)
Also:
male = Gender.selectBy(name="Male")[0]
alternateID is, of course, what's really intended in this situation.
> A big yuk. Makes table driven applications awful. ----
>
> 12. Similarly, if a class/table is just being used for integrity checks,
> returning an object rather than the referenced column within the row is
> clumsy. Assuming the schema in (11), one would like:
>
> target = Person.select(Person.q.name=="Geoff")
> print target.gender
> "Male"
>
> When an object is returned, one would have to know the name of the
> column and then dereference it:
>
> target = Person.select(Person.q.name=="Geoff")
> print target.gender.name
> "Male"
Yeah... but I'm still unsold. You can always do:
class Person(SQLObject):
def _get_gender(self):
return self._SO_get_gender().name
def _set_gender(self, value):
self._SO_set_gender(Gender.byName(value))
It's not that bad to use properties and create your own attributes, if
SQLObject's attributes don't suite you. People should do that more -- I
went out of my way to make it possible (and to document it)! This is
exactly the sort of thing it's there for.
> 13. Being able to add and drop columns at runtime is great. This removes
> the need to regenerate the tables in a live database. +++
>
> 14. Modeling (which seems rather hard to get into) seems to have very
> expressive ways to get complex joins. I haven't tried it at all, but
> have browsed the docs. Might be worth stealing some ideas from them. But
> gee, theyReallyLikeLongMethodNames! Or maybe I'm sensitive to them due
> to RSI...
I've only looked at Modeling a bit. But ouch... those functions and
methods are painful to read, not to mention write. Like:
from Modeling.FetchSpecification import FetchSpecification
from Modeling.Qualifier import qualifierWithQualifierFormat
qualifier=qualifierWithQualifierFormat('lastName=="Hugo"')
fetchSpec=FetchSpecification(entityName='Writer', qualifier=qualifier)
objects=ec.objectsWithFetchSpecification(fetchSpec)
That's a "simple" fetch. Hurts my head just having that on the page. Oh my:
aBook.addObjectToBothSidesOfRelationshipWithKey(anAuthor, 'author')
Okay. I guess that's enough of that, it's not fair to pick on other
ORMs. Besides joinSemantic, there's not a whole lot else that's not
just standard infrastructure. joinSemantic has outer joins, but I don't
know what they really mean to an ORM anyway. I mean, say you want all
people, and you also want their picture if they have one...
for person in Person.select():
image = person.image
# and do stuff, image may be None
# and don't tell me an outer join is easier to understand... ;)
If you want to do it in one query, well, you can't. ORMs tend to throw
out many more SQL queries. Some of them can probably be optimized, you
could do so using the selectResults keyword (for instance), but that
does require careful reading of the source to use... maybe that could be
made easier, but I don't think it can (or should) be made seemless. I
think a lot of uses outer joins are just fine as multiple queries,
that's something you should optimize based on an empirical need.
Ian
|