/* 
   EOSQLExpression.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Date: September 1996

   This file is part of the GNUstep Database Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "EOSQLExpression.h"
#include "common.h"
#include "EOAdaptor.h"
#include "EOAdaptorChannel.h"
#include "EOAdaptorContext.h"
#include "EOAttribute.h"
#include "EOAttributeOrdering.h"
#include "EOEntity.h"
#include "EOFExceptions.h"
#include "EOSQLQualifier.h"
#include "EORelationship.h"
#include <EOControl/EOFetchSpecification.h>
#include <EOControl/EONull.h>
#include <EOControl/EOQualifier.h>
#include <EOControl/EOSortOrdering.h>
#import "NGExtensions/NSException+misc.h"

#if LIB_FOUNDATION_LIBRARY
#  include <extensions/DefaultScannerHandler.h>
#  include <extensions/PrintfFormatScanner.h>
#else
#  include "DefaultScannerHandler.h"
#  include "PrintfFormatScanner.h"
#endif

/*
A SQL command is generated for an entity when you fetch, insert, update or
delete an object from the database. The command includes all the attributes
from entity.

The biggest problem in generation of SQL commands is the generation of SELECT
statements, because the tables have to be properly aliased. If an entity has
only simple attributes then you have a single table in the FROM clause. If the
entity has flattened attributes, then several tables appear in the FROM list;
each one with a different alias.

The algorithm uses a dictionary that has as keys either entities or
relationships. The values in dictionary are aliases.

For a simple attribute we insert in the above dictionary the attribute's entity
as key and an alias for it.

For a flattened attribute with the following definition: `toEntity1.toEntity2.
... toEntityn.attribute', we have to assign to each toEntityi object an alias.

Let's see how each component of the SELECT command is generated. The columns
list is generated by iterating over the attributes of entity. If the attribute
is simple, its entity is looked up in the aliases dictionary. If the attribute
is flattened, the alias of attribute is the alias of toEntityn. If the
attribute is derived, each component of it is generated applying the same
rules.

The FROM list is generated like this. For each key in the aliases dictionary:
* if the key is an entity, its external name is written followed by the alias
corresponding to entity
* if the key is a relationship, the external name of its destination entity is
written followed by the alias corresponding to relationship.

A little bit complicated is the WHERE clause. For each flattened attribute we
have to generate the logic expression that selects the attribute by expressing
the joins over several entities. The algorithm starts with the first
relationship.  An additional variable, named context is used. Its initial value
is the current entity. The expression

context-alias.source-attribute join-operator
            relationship-alias.destination-attribute

is added using the AND operator to the where clause. Then the context variable
is set to the current relationship. The algorithm continues with the next
relationship until there are no more relationships to process.

*/

static EONull *null = nil;
NSString *EOBindVariableNameKey        = @"name";
NSString *EOBindVariablePlaceHolderKey = @"placeHolder";
NSString *EOBindVariableAttributeKey   = @"attribute";
NSString *EOBindVariableValueKey       = @"value";


@interface EOInsertUpdateScannerHandler : DefaultScannerHandler
{
    NSString    *value;
    EOAttribute *attribute;
    EOAdaptor   *adaptor;
}

- (void)setValue:(NSString*)value
  attribute:(EOAttribute*)attribute
  adaptor:(EOAdaptor*)adaptor;
@end


@implementation EOInsertUpdateScannerHandler

- (id)init {
  [super init];

  specHandler['V']
    = [self methodForSelector:@selector(convertValue:scanner:)];
  return self;
}

- (void)dealloc {
  RELEASE(self->value);
  RELEASE(self->attribute);
  RELEASE(self->adaptor);
  [super dealloc];
}

- (void)setValue:(NSString *)_value
  attribute:(EOAttribute*)_attribute
  adaptor:(EOAdaptor*)_adaptor
{
    ASSIGNCOPY(self->value,     _value);
    ASSIGN(self->attribute, _attribute);
    ASSIGN(self->adaptor,   _adaptor);
}

- (NSString *)convertValue:(va_list *)pString scanner:(FormatScanner *)scanner{
  return (self->adaptor)
    ? [self->adaptor formatValue:(self->value ? self->value : (NSString *)null)
	             forAttribute:attribute]
    : (id)self->value;
}

@end /* EOInsertUpdateScannerHandler */


@implementation EOSQLExpression

+ (void)initialize {
  if (null == nil) null = [[NSNull null] retain];
}

- (id)initWithEntity:(EOEntity *)_entity {
  if ((self = [super init])) {
    ASSIGN(self->entity, _entity);
  }
  return self;
}
- (id)init {
  return [self initWithEntity:nil];
}

+ (id)deleteExpressionWithQualifier:(EOSQLQualifier*)qualifier
  channel:(EOAdaptorChannel*)channel
{
    EOSQLExpression* sqlExpression
        = AUTORELEASE([[self deleteExpressionClass] new]);
    [sqlExpression deleteExpressionWithQualifier:qualifier channel:channel];
    return sqlExpression;
}
+ (id)insertExpressionForRow:(NSDictionary *)row
  entity:(EOEntity*)_entity
  channel:(EOAdaptorChannel*)channel
{
    EOSQLExpression* sqlExpression
        = AUTORELEASE([[self insertExpressionClass] new]);
    
    [sqlExpression insertExpressionForRow:row entity:_entity channel:channel];
    return sqlExpression;
}

- (id)deleteExpressionWithQualifier:(EOSQLQualifier *)qualifier
  channel:(EOAdaptorChannel*)channel
{
  NSString *sql;
  
  self = [self initWithEntity:[qualifier entity]];
  
  self->adaptor = RETAIN([[channel adaptorContext] adaptor]);

  sql = [self assembleDeleteStatementWithQualifier:qualifier
              tableList:[entity externalName]
              whereClause:[self whereClauseForQualifier:qualifier]];
  
  self->content = [sql mutableCopy];
  [self finishBuildingExpression];
  
  return self;
}

- (id)insertExpressionForRow:(NSDictionary *)row
  entity:(EOEntity*)_entity
  channel:(EOAdaptorChannel*)channel
{
  NSString *sql;
  
  self = [self initWithEntity:_entity];

  self->adaptor = RETAIN([[channel adaptorContext] adaptor]);

  sql = [self assembleInsertStatementWithRow:row
              tableList:[entity externalName]
              columnList:[self columnListForRow:row]
              valueList:[self valueListForRow:row]];
  
  self->content = [sql mutableCopy];
  
  [self finishBuildingExpression];

  return self;
}

+ (id)selectExpressionForAttributes:(NSArray *)attributes
  lock:(BOOL)flag
  qualifier:(EOSQLQualifier *)qualifier
  fetchOrder:(NSArray *)fetchOrder
  channel:(EOAdaptorChannel *)channel
{
    EOSQLExpression* sqlExpression
        = AUTORELEASE([[self selectExpressionClass] new]);
    [sqlExpression selectExpressionForAttributes:attributes
                    lock:flag
                    qualifier:qualifier
                    fetchOrder:fetchOrder
                    channel:channel];
    return sqlExpression;
}

- (id)selectExpressionForAttributes:(NSArray *)attributes
  lock:(BOOL)flag
  qualifier:(EOSQLQualifier *)qualifier
  fetchOrder:(NSArray *)fetchOrder
  channel:(EOAdaptorChannel *)channel
{
    NSArray  *relationshipPaths;
    NSString *sql;
    
    self = [self initWithEntity:[qualifier entity]];
    
    self->adaptor = [[[channel adaptorContext] adaptor] retain];
    // self->content = [NSMutableString new]; // mh: BUG!!!
    
    relationshipPaths = [self relationshipPathsForAttributes:attributes
                              qualifier:qualifier
                              fetchOrder:fetchOrder];
    
    sql = [self assembleSelectStatementWithAttributes:attributes
                lock:flag
                qualifier:qualifier
                fetchOrder:fetchOrder
                selectString:
                  [qualifier usesDistinct] ? @"SELECT DISTINCT" : @"SELECT"
                columnList:
                  [self selectListWithAttributes:attributes 
			qualifier:qualifier]
                tableList:[self fromClause]
                whereClause:[self whereClauseForQualifier:qualifier]
                joinClause:
                  [self joinExpressionForRelationshipPaths:relationshipPaths]
                orderByClause:[self orderByClauseForFetchOrder:fetchOrder]
                lockClause:flag ? [self lockClause] : (NSString *)nil];
    
    self->content = [sql mutableCopy];
    
    [self finishBuildingExpression];
    
    return self;
}
+ (id)updateExpressionForRow:(NSDictionary *)row
  qualifier:(EOSQLQualifier *)qualifier
  channel:(EOAdaptorChannel *)channel
{
    EOSQLExpression* sqlExpression
        = AUTORELEASE([[self updateExpressionClass] new]);
    [sqlExpression updateExpressionForRow:row
                   qualifier:qualifier
                   channel:channel];
    return sqlExpression;
}

- (id)updateExpressionForRow:(NSDictionary *)row
  qualifier:(EOSQLQualifier *)qualifier
  channel:(EOAdaptorChannel *)channel
{
  NSString *sql;

  self = [self initWithEntity:[qualifier entity]];
  if (self->entity == nil) {
    [[[InvalidQualifierException alloc]
               initWithFormat:@"entity for qualifier %@ is nil",
                 [qualifier expressionValueForContext:nil]] raise];
  }
  
  self->adaptor = [[[channel adaptorContext] adaptor] retain];
  self->content = [[NSMutableString alloc] init];

  sql = [self assembleUpdateStatementWithRow:row
              qualifier:qualifier
              tableList:[entity externalName]
              updateList:[self updateListForRow:row]
              whereClause:[self whereClauseForQualifier:qualifier]];
  [self->content appendString:sql];
  
  [self finishBuildingExpression];

  return self;
}

- (void)dealloc {
  RELEASE(self->bindings);
  RELEASE(self->listString);
  RELEASE(self->whereClauseString);
  RELEASE(self->entity);
  RELEASE(self->adaptor);
  RELEASE(self->entitiesAndPropertiesAliases);
  RELEASE(self->fromListEntities);
  RELEASE(self->content);
  [super dealloc];
}

- (NSString *)selectListWithAttributes:(NSArray *)attributes
  qualifier:(EOSQLQualifier *)qualifier
{
    NSMutableString *selectList;
    NSEnumerator    *enumerator;
    BOOL            first = YES;
    EOAttribute     *attribute;

    selectList = [NSMutableString stringWithCapacity:128];

    enumerator = [attributes objectEnumerator];
    while ((attribute = [enumerator nextObject])) {
        if(first)
            first = NO;
        else
            [selectList appendString:@", "];

        [selectList appendString:[self expressionValueForAttribute:attribute]];
    }

    return selectList;
}

- (NSString *)fromClause {
    NSMutableString *fromClause;
    NSEnumerator *enumerator;
    BOOL first = YES;
    id key;

    fromClause = [NSMutableString stringWithCapacity:64];
    enumerator = [self->fromListEntities objectEnumerator];
    
    /* Compute the FROM list from all the aliases found in
       entitiesAndPropertiesAliases dictionary. Note that this dictionary
       contains entities and relationships. The last ones are there for
       flattened attributes over reflexive relationships. */
    while ((key = [enumerator nextObject])) {
        if (first)
            first = NO;
        else
            [fromClause appendString:@", "];

        if ([key isKindOfClass:[EORelationship class]]) {
            /* This key corresponds to a flattened attribute. */
            [fromClause appendFormat:@"%@ %@",
                            [[key destinationEntity] externalName],
                            [entitiesAndPropertiesAliases objectForKey:key]];
        }
        else {
            /* This is an EOEntity. */
            [fromClause appendFormat:@"%@ %@",
                            [key externalName],
                            [entitiesAndPropertiesAliases objectForKey:key]];
        }
    }

    return fromClause;
}

- (NSString *)whereClauseForQualifier:(EOSQLQualifier *)_qualifier {
  return [_qualifier expressionValueForContext:self];
}

- (NSString *)joinExpressionForRelationshipPaths:(NSArray *)relationshipPaths {
  NSMutableString *expression;
  NSEnumerator    *enumerator;
  NSMutableArray  *rels;
  NSArray         *relationshipPath;
  EORelationship  *relationship;
  BOOL            first = YES;

  expression = [NSMutableString stringWithCapacity:64];
  enumerator = [relationshipPaths objectEnumerator];
  rels       = [[NSMutableArray alloc] initWithCapacity:4];
    
  while ((relationshipPath = [enumerator nextObject]) != nil) {
    NSEnumerator *componentRelationshipsEnumerator;
    id context;

    componentRelationshipsEnumerator = [relationshipPath objectEnumerator];
    context = entity;

    while((relationship = [componentRelationshipsEnumerator nextObject])) {
      if (![rels containsObject:relationship]) {
        id sourceAttribute, destinationAttribute;

        [rels addObject:relationship];
            
        /* Compute the SQL expression string corresponding to each join in
           the relationship. */
        sourceAttribute = [self expressionValueForAttribute:
                                [relationship sourceAttribute]
                                context:context];
        destinationAttribute = [self expressionValueForAttribute:
                                     [relationship destinationAttribute]
                                     context:
                                     [relationship destinationEntity]];

        //NSLog(@"sourceAttribute %@", sourceAttribute);
        //NSLog(@"destinationAttribute %@", destinationAttribute);

        if (first) 
	  first = NO;
	else
	  [expression appendString:@" AND "];

        [expression appendString:[sourceAttribute description]];
        [expression appendString:@" = "];
        [expression appendString:[destinationAttribute description]];
      }
      /* Compute the next context which is the current relationship. */
      context = [relationship destinationEntity];
    }
  }
  [rels release];
  return expression;
}

- (NSString *)orderByClauseForFetchOrder:(NSArray *)fetchOrder
{
  int             i, count;
  NSMutableString *orderBy;
  
  if ((count = [fetchOrder count]) == 0)
    return @"";
  
  orderBy = (id)[NSMutableString stringWithCapacity:32];
  for(i = 0; i < count; i++) {
    id eorder;

    eorder = [fetchOrder objectAtIndex:i];
    
    if (i != 0) [orderBy appendString:@", "];
    
    if ([eorder isKindOfClass:[EOSortOrdering class]]) {
      EOSortOrdering *order;
      EOAttribute    *attribute;
      SEL            ordering;
      NSString       *fmt;
      
      order     = eorder;
      ordering  = [order selector];
      attribute = [self->entity attributeNamed:[order key]];

      if (sel_isEqual(ordering, EOCompareCaseInsensitiveAscending) ||
          sel_isEqual(ordering, EOCompareCaseInsensitiveDescending))
        fmt = @"LOWER(%@)";
      else
        fmt = @"%@";
      
      [orderBy appendFormat:fmt, [self expressionValueForAttribute:attribute]];

      if (sel_isEqual(ordering, EOCompareCaseInsensitiveAscending) ||
          sel_isEqual(ordering, EOCompareAscending)) {
        [orderBy appendString:@" ASC"];
      }
      else if (sel_isEqual(ordering, EOCompareCaseInsensitiveDescending) ||
               sel_isEqual(ordering, EOCompareDescending)) {
        [orderBy appendString:@" DESC"];
      }
    }
    else {
      EOAttributeOrdering *order;
      EOOrdering ordering;
      
      order    = eorder;
      ordering = [order ordering];
      
      [orderBy appendFormat:@"%@",
               [self expressionValueForAttribute:[order attribute]]];
      if (ordering != EOAnyOrder)
        [orderBy appendString:
                 ([order ordering] == EOAscendingOrder ? @" ASC" : @" DESC")];
    }
  }

  return orderBy;
}

- (NSString *)literalForAttribute:(EOAttribute *)_attribute
  withValue:(id)_value fromRow:(NSDictionary *)_row
{
  return (self->adaptor)
    ? [self->adaptor formatValue:(_value ? _value : (id)null)
	             forAttribute:_attribute]
    : (id)[_value stringValue];
}

- (id)updateListForRow:(NSDictionary *)row {
  PrintfFormatScanner          *formatScanner  = nil;
  EOInsertUpdateScannerHandler *scannerHandler = nil;
  NSMutableString *expression    = nil;
  NSEnumerator    *enumerator;
  NSString        *attributeName = nil;
  BOOL            first          = YES;

  enumerator    = [row keyEnumerator];
  expression = [NSMutableString stringWithCapacity:256];
    
  formatScanner = [[PrintfFormatScanner alloc] init];
  AUTORELEASE(formatScanner);
  scannerHandler = [[EOInsertUpdateScannerHandler alloc] init];
  AUTORELEASE(scannerHandler);

  [formatScanner setAllowOnlySpecifier:YES];
  [formatScanner setFormatScannerHandler:scannerHandler];

  while((attributeName = [enumerator nextObject])) {
    EOAttribute *attribute;
    NSString    *columnName   = nil;
    id          value         = nil;

    attribute = [entity attributeNamed:attributeName];
        
    NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
    columnName = adaptor
      ? (NSString *)[adaptor formatAttribute:attribute]
      : [attribute columnName];

    value = [row objectForKey:attributeName];
        
    value = [self literalForAttribute:attribute
		  withValue:value fromRow:row];
    
    if (first) first = NO;
    else [expression appendString:@", "];

    [expression appendString:columnName];
    [expression appendString:@" = "];
    [expression appendString:value];
  }

  return expression;
}

- (id)columnListForRow:(NSDictionary *)row {
  NSMutableString *expression;
  NSEnumerator    *enumerator;
  NSString        *attributeName = nil;
  BOOL            first          = YES;

  expression = [NSMutableString stringWithCapacity:128];
  enumerator = [row keyEnumerator];
    
  while ((attributeName = [enumerator nextObject])) {
    EOAttribute *attribute;
    NSString    *columnName;

    attribute = [entity attributeNamed:attributeName];
        
    NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
    columnName = adaptor
      ? (NSString *)[adaptor formatAttribute:attribute]
      : [attribute columnName];

    if (first) first = NO;
    else [expression appendString:@", "];

    [expression appendString:columnName];
  }
    
  return expression;
}

- (id)valueListForRow:(NSDictionary *)row  {
  EOInsertUpdateScannerHandler *scannerHandler;
  PrintfFormatScanner          *formatScanner;
  NSEnumerator                 *enumerator;
  NSString                     *attributeName  = nil;
  NSMutableString              *expression     = nil;
  BOOL                         first           = YES;

  formatScanner = [[PrintfFormatScanner alloc] init];
  AUTORELEASE(formatScanner);
  scannerHandler = [[EOInsertUpdateScannerHandler alloc] init];
  AUTORELEASE(scannerHandler);

  expression = [NSMutableString stringWithCapacity:256];
  enumerator     = [row keyEnumerator];

  [formatScanner setAllowOnlySpecifier:YES];
  [formatScanner setFormatScannerHandler:scannerHandler];

  while ((attributeName = [enumerator nextObject])) {
    EOAttribute *attribute;
    id          value;

    attribute    = [entity attributeNamed:attributeName];
    value        = [row objectForKey:attributeName];
        
    NSAssert1(attribute, @"attribute %@ should be non nil", attributeName);
    value = [self literalForAttribute:attribute
		  withValue:value fromRow:row];
    
    if (first) first = NO;
    else [expression appendString:@", "];

    [expression appendString:value];
  }
    
  return expression;
}

- (NSArray *)relationshipPathsForAttributes:(NSArray *)attributes
  qualifier:(EOSQLQualifier *)qualifier
  fetchOrder:(NSArray *)fetchOrder
{
    int          i, count;
    NSMutableSet *entities;
    NSMutableSet *relationshipPaths;
    NSEnumerator *enumerator;
    id           entityOrRelationship;

    entities          = [NSMutableSet set];
    relationshipPaths = [NSMutableSet set];
    
    NSAssert3(self->entity,
              @"entity should be non nil (attrs=%@, qual=%@, order=%@)",
              attributes, qualifier, fetchOrder);
    
    for (i = 0, count = [attributes count]; i < count; i++) {
      EOAttribute *attribute;

      attribute = [attributes objectAtIndex:i];
        
      if ([attribute entity] != entity) {
        [[[InvalidAttributeException alloc]
                    initWithFormat:@"all attributes must be from the same "
                        @"entity (attribute '%@' is not in '%@')",
                        [attribute name],
                        [entity name]] raise];
      }

      /* attribute is normal. */
      [entities addObject:[attribute entity]];
    }
    
    [relationshipPaths unionSet:[qualifier relationshipPaths]];
    [entities unionSet:[qualifier additionalEntities]];
    
    for (i = 0, count = [fetchOrder count]; i < count; i++) {
      EOAttribute *attribute;
      id eorder;

      eorder = [fetchOrder objectAtIndex:i];

      attribute = ([eorder isKindOfClass:[EOSortOrdering class]])
        ? [self->entity attributeNamed:[(EOSortOrdering *)eorder key]]
        : [(EOAttributeOrdering *)eorder attribute];
      
      if ([attribute entity] != entity) {
            [[[InvalidAttributeException alloc]
                    initWithFormat:@"all attributes must be from the same "
                        @"entity (attribute '%@' is not in '%@')",
                        [attribute name],
                        [entity name]] raise];
      }
    }

    entitiesAndPropertiesAliases = [NSMutableDictionary new];
    fromListEntities = [NSMutableArray new];
    enumerator = [entities objectEnumerator];
    i = 1;
    while ((entityOrRelationship = [enumerator nextObject])) {
        NSString* alias = [NSString stringWithFormat:@"t%d", i++];
        
        [entitiesAndPropertiesAliases setObject:alias
                                      forKey:entityOrRelationship];
        [fromListEntities addObject:entityOrRelationship];
    }

    return [relationshipPaths allObjects];
}

- (EOEntity *)entity {
  return self->entity;
}
- (id)finishBuildingExpression {
  return self;
}
- (NSString *)lockClause {
  return @"";
}

- (NSString *)expressionValueForAttribute:(EOAttribute *)attribute
  context:(id)context
{
    NSString *alias      = [entitiesAndPropertiesAliases objectForKey:context];
    NSString *columnName = nil;

    columnName = adaptor
        ? (NSString *)[adaptor formatAttribute:attribute]
        : [attribute columnName];

    return alias
        ? (NSString *)[NSString stringWithFormat:@"%@.%@", alias, columnName]
        : columnName;
}

- (NSString *)expressionValueForAttribute:(EOAttribute *)attribute {
  /* attribute is a normal attribute. Its alias is the alias
     of its entity. */
  return [self expressionValueForAttribute:attribute
	       context:[attribute entity]];
}

- (NSString *)expressionValueForAttributePath:(NSArray *)definitionArray {
    /* Take the alias of the last relationship. */
  id       relationship;
  NSString *alias;

  relationship
        = [definitionArray objectAtIndex:([definitionArray count] - 2)];
  alias = [entitiesAndPropertiesAliases objectForKey:relationship];
  
  return AUTORELEASE(([[NSString alloc]
                           initWithFormat:@"%@.%@",
                           alias, [[definitionArray lastObject] columnName]]));
}

- (NSString *)expressionValueForContext:(id<EOExpressionContext>)context {
    return self->content;
}

+ (Class)selectExpressionClass {
  return [EOSelectSQLExpression class];
}
+ (Class)insertExpressionClass {
  return [EOInsertSQLExpression class];
}
+ (Class)deleteExpressionClass {
  return [EODeleteSQLExpression class];
}
+ (Class)updateExpressionClass {
  return [EOUpdateSQLExpression class];
}

- (EOAdaptor *)adaptor {
  return self->adaptor;
}

/* description */

- (NSString *)description {
  NSMutableString *ms;

  ms = [NSMutableString stringWithCapacity:128];
  [ms appendFormat:@"<0x%p[%@]:\n", self, NSStringFromClass([self class])];
  
  if (self->entity)  [ms appendFormat:@"  entity=%@\n",  self->entity];
  if (self->adaptor) [ms appendFormat:@"  adaptor=%@\n", self->adaptor];
  if (self->content) [ms appendFormat:@"  content='%@\n'", self->content];
  
  if (self->entitiesAndPropertiesAliases) 
    [ms appendFormat:@"  aliases=%@\n", self->entitiesAndPropertiesAliases];
  if (self->fromListEntities)
    [ms appendFormat:@"  from-entities=%@\n", self->fromListEntities];

  if (self->whereClauseString)
    [ms appendFormat:@"  where=%@\n", self->whereClauseString];
  
  if (self->listString) [ms appendFormat:@"  list=%@\n",     self->listString];
  if (self->bindings)   [ms appendFormat:@"  bindings=%@\n", self->bindings];
  
  [ms appendString:@">"];
  return ms;
}

@end /* EOSQLExpression */


@implementation EOInsertSQLExpression
@end /* EOInsertSQLExpression */

@implementation EOUpdateSQLExpression
@end /* EOUpdateSQLExpression */

@implementation EODeleteSQLExpression
@end /* EODeleteSQLExpression */

@implementation EOSQLExpression(NewInEOF2)

+ (EOSQLExpression *)selectStatementForAttributes:(NSArray *)_attributes
  lock:(BOOL)_flag
  fetchSpecification:(EOFetchSpecification *)_fspec
  entity:(EOEntity *)_entity
{
    if (_fspec == nil) {
      [NSException raise:NSInvalidArgumentException
		   format:@"missing fetch specification argument .."];
    }
    if ([_attributes count] == 0) {
      [NSException raise:NSInvalidArgumentException
		   format:@"missing attributes for select .."];
    }

    return [self selectExpressionForAttributes:_attributes
                 lock:_flag
                 qualifier:[[_fspec qualifier] sqlQualifierForEntity:_entity]
                 fetchOrder:[_fspec sortOrderings]
                 channel:nil];
}

+ (EOSQLExpression *)expressionForString:(NSString *)_sql {
  EOSQLExpression *se;

  se = [[EOSQLExpression alloc] init];
  [se setStatement:_sql];
  return AUTORELEASE(se);
}

/* accessors */

- (void)setStatement:(NSString *)_stmt {
  id tmp;
  tmp = self->content;
  self->content = [_stmt mutableCopy];
  RELEASE(tmp);
}
- (NSString *)statement {
  return self->content;
}

- (NSString *)whereClauseString {
  return self->whereClauseString;
}

/* tables */

- (NSString *)tableListWithRootEntity:(EOEntity *)_entity {
  return ([self->fromListEntities count] > 0)
    ? [self fromClause]
    : [_entity externalName];
}

/* assembly */

- (NSString *)assembleDeleteStatementWithQualifier:(EOQualifier *)_qualifier
  tableList:(NSString *)_tableList
  whereClause:(NSString *)_whereClause
{
  NSMutableString *s;
  
  s = [NSMutableString stringWithCapacity:64];
  [s appendString:@"DELETE FROM "];
  [s appendString:_tableList];
  [s appendString:@" WHERE "];
  [s appendString:_whereClause];
  return s;
}

- (NSString *)assembleInsertStatementWithRow:(NSDictionary *)_row
  tableList:(NSString *)_tables
  columnList:(NSString *)_columns
  valueList:(NSString *)_values
{
  NSMutableString *s;
  
  s = [NSMutableString stringWithCapacity:256];
  [s appendString:@"INSERT INTO "];
  [s appendString:_tables];
  [s appendString:@" ("];
  [s appendString:_columns];
  [s appendString:@") VALUES ("];
  [s appendString:_values];
  [s appendString:@")"];
  return s;
}

- (NSString *)assembleSelectStatementWithAttributes:(NSArray *)_attributes
  lock:(BOOL)_lock
  qualifier:(EOQualifier *)_qualifier
  fetchOrder:(NSArray *)_fetchOrder
  selectString:(NSString *)_selectString
  columnList:(NSString *)_columns
  tableList:(NSString *)_tables
  whereClause:(NSString *)_whereClause
  joinClause:(NSString *)_joinClause
  orderByClause:(NSString *)_orderByClause
  lockClause:(NSString *)_lockClause
{
  NSMutableString *s;
  unsigned wlen, jlen;

#if 0
#warning DEBUG LOG, REMOVE!
  [self logWithFormat:@"%s: '%@': %@", __PRETTY_FUNCTION__, _whereClause,self];
#endif
  
  s = [NSMutableString stringWithCapacity:256];
  
  [s appendString:_selectString ? _selectString : (NSString *)@"SELECT"];
  [s appendString:@" "];
  [s appendString:_columns];
  [s appendString:@" FROM "];
  [s appendString:_tables];
  
  if ([_lockClause length] > 0) {
    [s appendString:@" "];
    [s appendString:_lockClause];
  }
  
  wlen = [_whereClause length];
  jlen = [_joinClause  length];

  if ((wlen > 0) || (jlen > 0))
    [s appendString:@" WHERE "];
  
  if (wlen > 0)
    [s appendString:_whereClause];

  if ((wlen > 0) && (jlen > 0))
    [s appendString:@" AND "];

  if (jlen > 0)
    [s appendString:_joinClause];
  
  if ([_orderByClause length] > 0) {
    [s appendString:@" ORDER BY "];
    [s appendString:_orderByClause];
  }
  
  return s;
}

- (NSString *)assembleUpdateStatementWithRow:(NSDictionary *)_row
  qualifier:(EOQualifier *)_qualifier
  tableList:(NSString *)_tables
  updateList:(NSString *)_updates
  whereClause:(NSString *)_whereClause
{
  NSMutableString *s;

  s = [NSMutableString stringWithCapacity:256];

  [s appendString:@"UPDATE "];
  [s appendString:_tables];
  [s appendString:@" SET "];
  [s appendString:_updates];
  [s appendString:@" WHERE "];
  [s appendString:_whereClause];
  
  return s;
}

- (NSString *)assembleJoinClauseWithLeftName:(NSString *)_leftName
  rightName:(NSString *)_rightName
  joinSemantic:(EOJoinSemantic)_semantic
{
  NSMutableString *s;
  
  s = [NSMutableString stringWithCapacity:64];
  [s appendString:_leftName];
  switch (_semantic) {
    case EOInnerJoin:
      [s appendString:@" = "];
      break;
    case EOFullOuterJoin:
      [s appendString:@" *=* "];
      break;
    case EOLeftOuterJoin:
      [s appendString:@" *= "];
      break;
    case EORightOuterJoin:
      [s appendString:@" =* "];
      break;
  }
  [s appendString:_rightName];
  return s;
}

/* attributes */

- (NSString *)sqlStringForAttribute:(EOAttribute *)_attribute {
  NSLog(@"ERROR(%s): subclasses need to override this method!",
	__PRETTY_FUNCTION__);
  return nil;
}

- (NSString *)sqlStringForAttributePath:(NSString *)_attrPath {
  NSLog(@"ERROR(%s): subclasses need to override this method!",
	__PRETTY_FUNCTION__);
  return nil;
}

- (NSString *)sqlStringForAttributeNamed:(NSString *)_attrName {
  EOAttribute *a;

  if ((a = [[self entity] attributeNamed:_attrName]))
    return [self sqlStringForAttribute:a];
  
  return [self sqlStringForAttributePath:_attrName];
}

/* bind variables */

+ (BOOL)useBindVariables {
  return NO;
}
- (BOOL)mustUseBindVariableForAttribute:(EOAttribute *)_attr {
  return NO;
}
- (BOOL)shouldUseBindVariableForAttribute:(EOAttribute *)_attr {
  return NO;
}

- (NSMutableDictionary *)bindVariableDictionaryForAttribute:(EOAttribute *)_a
  value:(id)_value
{
  NSMutableDictionary *d;
  
  d = [NSMutableDictionary dictionaryWithCapacity:8];
  [d setObject:_a                         forKey:EOBindVariableAttributeKey];
  [d setObject:_value ? _value : (id)null forKey:EOBindVariableValueKey];
  return d;
}

- (void)addBindVariableDictionary:(NSMutableDictionary *)_dictionary {
  if (self->bindings == nil)
    self->bindings = [[NSMutableArray alloc] init];
}
- (NSArray *)bindVariableDictionaries {
  return self->bindings;
}

/* values */

+ (NSString *)formatValue:(id)_value forAttribute:(EOAttribute *)_attribute {
  return _value != nil ? _value : (id)null;
}

- (NSString *)sqlStringForValue:(id)_value attributeNamed:(NSString *)_attrName {
  NSMutableDictionary *bindVars;
  EOAttribute *attribute;

  attribute = [[self entity] attributeNamed:_attrName];

  if ([self mustUseBindVariableForAttribute:attribute])
    bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value];
  else if ([[self class] useBindVariables] &&
           [self shouldUseBindVariableForAttribute:attribute]) {
    bindVars = [self bindVariableDictionaryForAttribute:attribute value:_value];
  }
  else
    bindVars = nil;

  if (bindVars) {
    [self addBindVariableDictionary:bindVars];
    return [bindVars objectForKey:EOBindVariablePlaceHolderKey];
  }
  
  return [[self class] formatValue:(_value ? _value : (id)null)
		       forAttribute:attribute];
}

+ (NSString *)sqlPatternFromShellPattern:(NSString *)_pattern {
  unsigned len;

  if ((len = [_pattern length]) > 0) {
    unsigned   cstrLen = [_pattern cStringLength];
    char       cstrBuf[cstrLen + 1];
    const char *cstr;
    char       buf[len * 3 + 1];
    unsigned   i;
    BOOL       didSomething = NO;

    [_pattern getCString:cstrBuf];
    cstr = cstrBuf;
    
    for (i = 0; *cstr; cstr++) {
      switch (*cstr) {
        case '*':
          buf[i] = '%'; i++; didSomething = YES;
          break;
        case '?':
          buf[i] = '_'; i++; didSomething = YES;
          break;
          
        case '%':
          buf[i] = '['; i++;
          buf[i] = '%'; i++;
          buf[i] = ']'; i++;
          didSomething = YES;
          break;
          
        case '_':
          buf[i] = '['; i++;
          buf[i] = '_'; i++;
          buf[i] = ']'; i++;
          didSomething = YES;
          break;
          
        default:
          buf[i] = *cstr;
          i++;
          break;
      }
    }
    buf[i] = '\0';
    
    return (didSomething)
      ? (NSString *)[NSString stringWithCString:buf length:i]
      : (NSString *)_pattern;
  }
  return _pattern;
}

/* SQL formats */

+ (NSString *)formatSQLString:(NSString *)_sqlString format:(NSString *)_fmt {
  return _sqlString;
}

/* qualifier operators */

- (NSString *)sqlStringForSelector:(SEL)_selector value:(id)_value {
  if ((_value == null) || (_value == nil)) {
    if (sel_isEqual(_selector, EOQualifierOperatorEqual))
      return @"is";
    else if (sel_isEqual(_selector, EOQualifierOperatorNotEqual))
      return @"is not";
  }
  else {
    if (sel_isEqual(_selector, EOQualifierOperatorEqual))
      return @"=";
    else if (sel_isEqual(_selector, EOQualifierOperatorNotEqual))
      return @"<>";
  }
  
  if (sel_isEqual(_selector, EOQualifierOperatorLessThan))
    return @"<";
  else if (sel_isEqual(_selector, EOQualifierOperatorGreaterThan))
    return @">";
  else if (sel_isEqual(_selector, EOQualifierOperatorLessThanOrEqualTo))
    return @"<=";
  else if (sel_isEqual(_selector, EOQualifierOperatorGreaterThanOrEqualTo))
    return @">=";
  else if (sel_isEqual(_selector, EOQualifierOperatorLike))
    return @"LIKE";
  else {
    return [NSString stringWithFormat:@"UNKNOWN<%@>",
                       NSStringFromSelector(_selector)];
  }
}

/* qualifiers */

- (NSString *)sqlStringForKeyComparisonQualifier:(EOKeyComparisonQualifier *)_q {
  NSMutableString *s;
  NSString        *sql;
  
  s = [NSMutableString stringWithCapacity:64];
  
  sql = [self sqlStringForAttributeNamed:[_q leftKey]];
  sql = [[self class] formatSQLString:sql format:nil];
  [s appendString:sql];
  
  [s appendString:@" "];
  [s appendString:[self sqlStringForSelector:[_q selector] value:nil]];
  [s appendString:@" "];
  
  sql = [self sqlStringForAttributeNamed:[_q rightKey]];
  sql = [[self class] formatSQLString:sql format:nil];
  [s appendString:sql];
  
  return s;
}

- (NSString *)sqlStringForKeyValueQualifier:(EOKeyValueQualifier *)_q {
  NSMutableString *s;
  NSString        *sql;
  id              v;

  v = [_q value];
  s = [NSMutableString stringWithCapacity:64];

  sql = [self sqlStringForAttributeNamed:[_q key]];
  sql = [[self class] formatSQLString:sql format:nil];
  [s appendString:sql];
  
  [s appendString:@" "];
  sql = [self sqlStringForSelector:[_q selector] value:v];
  [s appendString:sql];
  [s appendString:@" "];
  
  if (([_q selector] == EOQualifierOperatorLike) ||
      ([_q selector] == EOQualifierOperatorCaseInsensitiveLike))
    v = [[self class] sqlPatternFromShellPattern:v];
  
  sql = [self sqlStringForValue:v attributeNamed:[_q key]];
  [s appendString:sql];
  return s;
}

- (NSString *)sqlStringForNegatedQualifier:(EOQualifier *)_q {
  NSMutableString *s;
  NSString *sql;

  sql = [(id<EOQualifierSQLGeneration>)_q sqlStringForSQLExpression:self];
  s = [NSMutableString stringWithCapacity:[sql length] + 8];
  [s appendString:@"NOT ("];
  [s appendString:sql];
  [s appendString:@")"];
  return s;
}

- (NSString *)sqlStringForConjoinedQualifiers:(NSArray *)_qs {
  NSMutableString *s;
  unsigned i, count;
  id (*objAtIdx)(id,SEL,unsigned);

  objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)];
  for (i = 0, count = [_qs count], s = nil; i < count; i++) {
    id<EOQualifierSQLGeneration> q;
    
    q = objAtIdx(self, @selector(objectAtIndex:), i);

    if (s == nil)
      s = [NSMutableString stringWithCapacity:128];
    else
      [s appendString:@" AND "];
    
    [s appendString:[q sqlStringForSQLExpression:self]];
  }
  return s;
}

- (NSString *)sqlStringForDisjoinedQualifiers:(NSArray *)_qs {
  NSMutableString *s;
  unsigned i, count;
  id (*objAtIdx)(id,SEL,unsigned);

  objAtIdx = (void *)[_qs methodForSelector:@selector(objectAtIndex:)];
  for (i = 0, count = [_qs count], s = nil; i < count; i++) {
    id<EOQualifierSQLGeneration> q;
    
    q = objAtIdx(self, @selector(objectAtIndex:), i);

    if (s == nil)
      s = [NSMutableString stringWithCapacity:128];
    else
      [s appendString:@" OR "];
    
    [s appendString:[q sqlStringForSQLExpression:self]];
  }
  return s;
}

/* list strings */

- (NSMutableString *)listString {
  if (self->listString == nil)
    self->listString = [[NSMutableString alloc] initWithCapacity:128];
  return self->listString;
}

- (void)appendItem:(NSString *)_itemString toListString:(NSMutableString *)_ls {
  if ([_ls length] > 0)
    [_ls appendString:@","];
  [_ls appendString:_itemString];
}

/* deletes */

- (void)prepareDeleteExpressionForQualifier:(EOQualifier *)_qual {
  NSString *tableList, *sql;
  
  self->whereClauseString =
    [[(id<EOQualifierSQLGeneration>)_qual sqlStringForSQLExpression:self] copy];

  tableList = [self tableListWithRootEntity:[self entity]];
  
  sql = [self assembleDeleteStatementWithQualifier:_qual
              tableList:tableList
              whereClause:[self whereClauseString]];
  
  [self setStatement:sql];
}

/* updates */

- (void)addUpdateListAttribute:(EOAttribute *)_attr value:(NSString *)_value {
  NSMutableString *s;

  s = [[NSMutableString alloc] initWithCapacity:32];
  [s appendString:[_attr columnName]];
  [s appendString:@"="];
  _value = [[self class] formatSQLString:_value format:nil];
  [s appendString:_value];
  
  [self appendItem:s toListString:[self listString]];
  RELEASE(s);
}

- (void)prepareUpdateExpressionWithRow:(NSDictionary *)_row
  qualifier:(EOQualifier *)_qual
{
  NSEnumerator *keys;
  NSString     *key;
  NSString     *tableList, *sql;
  
  keys = [_row keyEnumerator];
  while ((key = [keys nextObject])) {
    EOAttribute *attribute;
    id          value;
    
    attribute = [self->entity attributeNamed:key];
    value     = [_row objectForKey:key];
    
    [self addUpdateListAttribute:attribute value:value];
  }
  
  self->whereClauseString =
    [[(id<EOQualifierSQLGeneration>)_qual sqlStringForSQLExpression:self] copy];

  tableList = [self tableListWithRootEntity:[self entity]];

  sql = [self assembleUpdateStatementWithRow:_row
              qualifier:_qual
              tableList:tableList
              updateList:[self listString]
              whereClause:[self whereClauseString]];
  
  [self setStatement:sql];
}

@end /* EOSQLExpression(NewInEOF2) */
