Hi,
sorry it took me so long replying to this, but it needed a certain amount
of thought. There might be some inconsistencies in what I have added, but
I wanted this out now.
Personally, I do not see this in the 3.2 release, it touches too many
internals.
More comments inserted below.
Thomas
On Fri, 26 Aug 2005, Greg Monroe wrote:
Thomas and I have been having some discussions relating to quick and
dirty enhancement patch I submitted to add DB independent concatenation
to Torque ( http://issues.apache.org/scarab/issues/id/TRQS317 ).
Rather than clog up Scarab with multiple comments, it seems like it's
time to move this to a Torque-dev thread.
IMHO, The main infrastructure issues that seems to be coming out of this
are:
- How to support general DB independent common functions, e.g. concat
and the like.
- How to integerate functions into the Criteria model.
- How to reconcile current confusion of columns as strings and
functions.
Some of the design points to concider that have been made are:
- DB specific information should be in the DB adapter class(es).
- KISS (Keep it simple stupid) because that's one of the beauties of
Torque
- Downward compatiblity, e.g. thou shalt not remove methods (but
deprecation may be OK).
- Minimize the number of methods added to classes that already have too
many.
That said, let me attempt to layout my current thoughts on how to
achieve this...
First, how about creating a Column interface. This would be a
definition of the common methods that the Criteria model needs in order
to eventually create the SQL statement string. This would be based on a
few of the ColumnMap class methods (so ColumnMap already impliments it)
and maybe one or two others (like equals/hashCode maybe?). Having an
interface would allow for function classes that implement the Column
interface to be used in Criteria methods just like columns.
A quick first pass at what methods the column interface would have is:
/**
* Real Columns would return table.column
* Functions would return appropriate string like
* users.first_name || ' ' || users.last_name
*/
getFullyQualifiedName()
/**
* Real columns = column name
* Functions = fullyQualified name
*/
getColumnName()
/**
* Real columns would return at least Object();
* Functions would return a subclass of something like SQLFunction so
typeof can be
* used to determine actions.
*/
getType()
/**
* Real columns = table name
* Functions = null?... Maybe functions need to have a "main" table
specified when created?
*/
getTableName()
I do not think that anything used in the TableMap would be a good start.
The reason is that the TableMap cares about structure (e.g. "column X
contains a String and it is a foreign key to another Table and it has a
size of 255"), but this is not what we need here. We need the Column
object for SQL generation. Up to now, we have used strings there, like
e.g. TableName.ColumnName. (or even SchemaName.TableName.ColumnName) so as
first guess, the column interface will contain at least getters for
schemaName, tableName, and columnName.
For the getters, a real column would have at least return a valid
tableName and a valid columnName. To see what a "composed" column might
return, we need to consider what is happening with the column. It is used
for generation of the select clause and of the from clause of a sql
statement.
In my opinion, neither clause should be generated from the column object
itself, but rather from a SQL builder (as it is done now for similar
structures). This is the classical MVC pattern: Separate (SQL) view from
(Column) model, with all its advantages.
For building the select clause, the column would need to tell the SQL
generation classes what kind of column it is. This can be decided by the
class of the implementing object or by some function like getType()
Probably there will be different implementations for normal columns,
function columns etc, so the sql generator would have to use switches by
instanceof anyway, so there is no need for the getType() in the interface.
For building the from clause, we need to know which tables are used in the
column. For normal columns, this is easy. For functions, there might be
several "normal" columns involved. So the function column will probably
keep a List of column "arguments". For building the from clause, all the
contained columns should be returned by the column object, so an
additional method in the interface is needed:
public List getContainedColumns();
which returns a list of columns and is called in building the from clause
if getTableName() returns null.
Perhaps the getColumnName() should be renamed toString(). As explained
above, in my opinion it sould only return valid SQL for simple columns,
and a string with debugging info for function columns.
How would the ColumnInterface be used:
With a Column interface, you could add a small set of methods to the
Criteria
function like:
add( ColumnInterface column, Object comparitor, SqlEnum enum );
Or alternatively, have a ColumnCriteria (or some such name) class that
extended Criteria with a more "complete" set of Column based methods.
(The Java doc will be cleaner this way, but IDE code helpers, e g.
Eclipse, will still show lots of methods...)
V3.3 idea... create an Interface and/or base Criteria object with the
methods needed for SQL statement creation, make this the type for Peer
calls. Then you could have more Criteria (Criterion?) objects with fewer
methods, e.g. JoinCriteria.add... or OrderCriteria.. that get combined
to make the end statement criteria.
The first way of using the column object obviously stuffs the criteria
object with more methods.
The second way has the disadvantage that people will tend to use the
more powerful object anyway. What use is there for the less powerful
object ?
For the third way, this would lead to a totally different way of building
criteria. You would not use criteria.add(someColumn, someValue) and
criteria.addOrderBy(someColumn) but
criteria.add(new Condition(someColumn,someValue)) or criteria.add(new
OrderBy(someColumn)), if I understood you correctly. It is certainly an
interesting idea. Perhaps it could be used as an alternative to criteria,
but it is a lot of work and I am not sure if it has other advantages
besides from the (new) Criteria object being less stuffed.
Still another idea : Why should columns represented by strings at all ?
Constants like SomePeer.SOME_COLUMN could also be column objects instead
of Strings. Then, one could deprecate all the methods in criteria using
strings as column namens, and replace them with methods using the column
interface. This will be source compatible for people using columns the
usual way, like criteria.add(SomePeer.SOME_COLUMN, someValue). To retain
the flexibility by using strings, one could add a method which parses
Strings into column objects.
How do you get a real column object
ColumnInterface c = (ColumnInterface)
TablePeer.getTableMap().getColumn(name);
works now. but adding a ColumnInterface getColumn(name) function to
BasePeer
would be nice.
The SQL Functions layout
Taking the previous conversations into mind, how about this:
For each function that Torque supports there would be class that
implement the column interface. These would supply function specific
methods for general use. For example, with concat, there could methods
like concate( ColumnInterface, String) or concate(String, String).
Specifying ColumnInterface and String as needed would allow for the
appropriate handling of parameters that need quoting, etc. This also
makes the functions more "user friendly" because the functions method
format is more like the underlying SQL being created.
Hm, having a separate class for each function will add a lot of
if (xxx instanceof yyy)
in the sql creation. But perhaps one can have an underlying base class
which has a function name, and a List of arguments.
These function classes will have to be constructed with information that
allows it to find the DB adapter being used. E.g., new
SQLConcat(Criteria c) or new SQLConcat(DB adapter) and probably need to
be built on a base abstract class (so getType() returns a constant
typeof).
If you do not generate SQL in the functions themselves, (see above,
separate model from view), you would not need this. The criteria knows
which DB should be used, no need for the function to know it.
The responsiblity of these function classes is simply to process the
various method arguments and convert them to a common SQLFunction
Interface format. The implimented ColumnInterface methods like
getQualifiedName, would get a DB Specific SQLFunction implimentation
from an Adapter factory method.
Here's some psuedo code to illustrate this:
class SQLConcat impliments ColumnInterface {
SQLConcat( Criteria c ) {
setDBAdapter( Torque.getDB(c.getDbName()));
}
void concat( column, string ) {
clearArgs();
addArg(column.getQualifiedName());
addArg(SQLExpression.quoteAndEscapeText(string),getDB());
}
String getQualifiedName() {
SQLFunction func = getDB().makeFunction(DB.CONCAT);
return func.getSQLString(getArgs());
}
}
I'd suggest that the DB base class could have the SQL standard functions
implimented as inner classes so that only adapters that need different
implimentations would have to override them. Here's some psuedo code:
class DB {
static String CONCAT = "concat";
Class Concat impliments SQLFunction {
getSQLString( String[] args ) {
StringBuf result = new StringBuf();
result.append(arg1);
for other args {
result.append("||").append(arg#) }
return result.toString();
}
}
makeFunction(String type ) throws IllegalArgumentException {
if ( type.equals(CONCAT) ) { return new DB.Concat() };
}
}
Adding the functions to the db adapter is a good idea. If one separates
sql generation from the column model, the above will not work. But why not
add a method like
public String getFunction(SQLEnum functionName, List functionArguments)
which has a default implementation and can be overridden in each Adapter.
Some Psuedo End User code
Criteria c = new Criteria();
ColumnInterface last = UsersPeer.getColumn(UsersPeer.LASTNAME);
ColumnInterface first = UsersPeer.getColumn(UsersPeer.FIRSTNAME);
ColumnInterface ou = UsersPeer.getColumn(UsersPeer.OU);
SQLConcat fullname = new SQLConcat(c);
fullname.concat(first," ",last); // Maybe should use StringBuffer
format?
c.add(fullname,"%ney Rub%", SqlEnum.LIKE);
c.add(ou,"o=Slate.com".SqlEnum.EQUALS);
Thus endth the design acording to me....Thoughts?..confusion?...
Greg
Duke CE Privacy Statement
Please be advised that this e-mail and any files transmitted with it are
confidential communication or may otherwise be privileged or confidential and
are intended solely for the individual or entity to whom they are addressed.
If you are not the intended recipient you may not rely on the contents of this
email or any attachments, and we ask that you please not read, copy or
retransmit this communication, but reply to the sender and destroy the email,
its contents, and all copies thereof immediately. Any unauthorized
dissemination, distribution or copying of this communication is strictly
prohibited.
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]