require 'digest/sha1'

module RdfContext
  autoload :AbstractStore, File.join(File.dirname(__FILE__), 'abstract_store')

  # SQL-92 formula-aware implementation of an RDF Store.
  # It stores it's triples in the following partitions:
  # - Asserted non rdf:type statements
  # - Asserted literal statements
  # - Asserted rdf:type statements (in a table which models Class membership). The motivation for this partition is primarily query speed and scalability as most graphs will always have more rdf:type statements than others
  # - All Quoted statements
  #
  #  In addition it persists namespace mappings in a seperate table
  #
  # Based on Python RdfLib AbstractSQLStore
  class AbstractSQLStore < AbstractStore
    include TermUtils
    
    COUNT_SELECT   = 0
    CONTEXT_SELECT = 1
    TRIPLE_SELECT  = 2
    TRIPLE_SELECT_NO_ORDER = 3

    ASSERTED_NON_TYPE_PARTITION = 3
    ASSERTED_TYPE_PARTITION     = 4
    QUOTED_PARTITION            = 5
    ASSERTED_LITERAL_PARTITION  = 6

    FULL_TRIPLE_PARTITIONS = [QUOTED_PARTITION,ASSERTED_LITERAL_PARTITION]

    INTERNED_PREFIX = 'kb_'
    
    STRONGLY_TYPED_TERMS = false
    
    # Create a new AbstractSQLStore Store, should be subclassed
    # @param [URIRef] identifier
    # @param[Hash] configuration Specific to type of storage
    # @return [AbstractSQLStore]
    def initialize(identifier = nil, configuration = {})
      @literalCache = {}
      @otherCache = {}
      @bnodeCache = {}
      @uriCache = {}
      
      @autocommit_default = true
      
      raise StoreException.new("Identifier must be nil or a URIRef") if identifier && !identifier.is_a?(URIRef)
      @identifier = identifier || URIRef.new("file:/#{Dir.getwd}")
      
      @internedId = INTERNED_PREFIX + Digest::SHA1.hexdigest(@identifier.to_s)[0..9] # Only first 10 bytes of digeset
      
      @db = configuration.empty? ? nil : open(configuration)
    end

    # Supports contexts
    # @return [true]
    def context_aware?; true; end
    
    # Supports formulae
    # @return [true]
    def formula_aware?; true; end

    # Supports transactions
    # @return [true]
    def transaction_aware?; true; end

    # Close the store
    # @param [Boolean] commit_pending_transactions (false)
    # @return [void]
    def close(commit_pending_transactions = false)
      @db.commit if commit_pending_transactions && @db.transaction_active?
      @db.close
    end
    
    # Add a triple to the store
    # Add to default context, if context is nil
    #
    # @param [Triple] triple
    # @param [Graph] context (nil)
    # @param [Boolean] quoted (false) A quoted triple, for Formulae
    # @return [Triple]
    def add(triple, context = nil, quoted = false)
      context ||= @identifier
      executeSQL("SET AUTOCOMMIT=0") if @autocommit_default
      
      if quoted || triple.predicate != RDF_TYPE
        # Quoted statement or non rdf:type predicate
        # Check if object is a literal
        if triple.object.is_a?(Literal)
          addCmd, *params = self.buildLiteralTripleSQLCommand(triple, context)
        else
          addCmd, *params = self.buildTripleSQLCommand(triple, context, quoted)
        end
      elsif triple.predicate == RDF_TYPE
        addCmd, *params = self.buildTypeSQLCommand(triple.subject, triple.object, context)
      end
      
      executeSQL(addCmd, params)
    end
    
    # Remove a triple from the context and store
    #
    # if subject, predicate and object are nil and context is not nil, the context is removed
    #
    # @param [Triple] triple
    # @param [Graph] context (nil)
    # @return [void]
    def remove(triple, context = nil)
      if context
        if triple.subject == nil && triple.predicate.nil? && triple.object.nil?
          return remove_context(context)
        end
      end
      
      if triple.predicate.nil? || triple.predicate != RDF_TYPE
        # Remove predicates other than rdf:type
        if !STRONGLY_TYPED_TERMS || triple.object.is_a?(Literal)
          clauseString, *params = self.buildClause(literal_table,triple,context)
          if !clauseString.empty?
            cmd = "DELETE FROM #{literal_table} #{clauseString}"
          else
            cmd = "DELETE FROM #{literal_table}"
          end
          executeSQL(_normalizeSQLCmd(cmd), params)
        end
        
        [quoted_table, asserted_table].each do |table|
          # If asserted non rdf:type table and obj is Literal, don't do anything (already taken care of)
          next if table == asserted_table && triple.object.is_a?(Literal)
          
          clauseString, *params = self.buildClause(table, triple, context)
          if !clauseString.empty?
            cmd = "DELETE FROM #{table} #{clauseString}"
          else
            cmd = "DELETE FROM #{table}"
          end
          executeSQL(_normalizeSQLCmd(cmd), params)
        end
      elsif triple.predicate == RDF_TYPE || triple.predicate.nil?
        # Need to check rdf:type and quoted partitions (in addition perhaps)
        clauseString, *params = self.buildClause(asserted_type_table,triple,context, true)
        if !clauseString.empty?
          cmd = "DELETE FROM #{asserted_type_table} #{clauseString}"
        else
          cmd = "DELETE FROM #{asserted_type_table}"
        end
        executeSQL(_normalizeSQLCmd(cmd), params)

        clauseString, *params = self.buildClause(quoted_table,triple,context)
        if !clauseString.empty?
          cmd = "DELETE FROM #{quoted_table} #{clauseString}"
        else
          cmd = "DELETE FROM #{quoted_table}"
        end
        executeSQL(_normalizeSQLCmd(cmd), params)
      end
    end
    
    # A generator over all the triples matching pattern.
    # 
    # quoted table::                <id>_quoted_statements
    # asserted rdf:type table::     <id>_type_statements
    # asserted non rdf:type table:: <id>_asserted_statements
    # 
    # triple columns: subject,predicate,object,context,termComb,objLanguage,objDatatype
    # class membership columns: member,klass,context termComb
    # 
    # @todo  These union all selects *may* be further optimized by joins
    #
    # @param [Triple] triple
    # @param [Graph] context (nil)
    # @return [Array<Triplle>]
    # @raise [StoreException] Not Implemented
    # @yield [triple, context]
    # @yieldparam [Triple] triple
    # @yieldparam [Graph] context
    def triples(triple, context = nil)  # :yields: triple, context
      parameters = []
      
      if triple.predicate == RDF_TYPE
        # select from asserted rdf:type partition and quoted table (if a context is specified)
        clauseString, *params = self.buildClause('typeTable',triple,context, true)
        parameters += params
        selects = [
          [
            asserted_type_table,
            'typeTable',
            clauseString,
            ASSERTED_TYPE_PARTITION
          ],
        ]
      # elsif triple.predicate.is_a?(REGEXTerm) && triple.predicate.compiledExpr.match(RDF_TYPE) || triple.predicate.nil?
      elsif triple.predicate.nil?
        # Select from quoted partition (if context is specified), literal partition if (obj is Literal or None) and asserted non rdf:type partition (if obj is URIRef or None)
        selects = []
        if !STRONGLY_TYPED_TERMS || triple.object.is_a?(Literal) || triple.object.nil?
          clauseString, *params = self.buildClause('literal',triple,context)
          parameters += params
          selects += [
            [
              literal_table,
              'literal',
              clauseString,
              ASSERTED_LITERAL_PARTITION
            ]
          ]
        end
      
        if !triple.object.is_a?(Literal) || triple.object.nil?
          clauseString, *params = self.buildClause('asserted',triple,context)
          parameters += params
          selects += [
            [
              asserted_table,
              'asserted',
              clauseString,
              ASSERTED_NON_TYPE_PARTITION
            ]
          ]
        end

        clauseString, *params = self.buildClause('typeTable',Triple.new(triple.subject, RDF_TYPE, triple.object),context, true)
        parameters += params
        selects += [
          [
            asserted_type_table,
            'typeTable',
            clauseString,
            ASSERTED_TYPE_PARTITION
          ]
        ]
      elsif triple.predicate
        # select from asserted non rdf:type partition (optionally), quoted partition (if context is speciied), and literal partition (optionally)
        selects = []
        if !STRONGLY_TYPED_TERMS || triple.object.is_a?(Literal) || triple.object.nil?
          clauseString, *params = self.buildClause('literal',triple,context)
          parameters += params
          selects += [
            [
              literal_table,
              'literal',
              clauseString,
              ASSERTED_LITERAL_PARTITION
            ]
          ]
        end
    
        if !triple.object.is_a?(Literal) || triple.object.nil?
          clauseString, *params = self.buildClause('asserted',triple,context)
          parameters += params
          selects += [
            [
              asserted_table,
              'asserted',
              clauseString,
              ASSERTED_NON_TYPE_PARTITION
            ]
          ]
        end
      end
      
      if context
        clauseString, *params = self.buildClause('quoted',triple,context)
        parameters += params
        selects += [
          [
            quoted_table,
            'quoted',
            clauseString,
            QUOTED_PARTITION
          ]
        ]
      end
      
      q = _normalizeSQLCmd(unionSELECT(selects))
      results = []
      executeSQL(q, parameters) do |row|
        triple, graphKlass, idKlass, graphId = extractTriple(row, context)
        currentContext = graphKlass.new(:store => self, :identifier => idKlass.new(graphId))
        if block_given?
          yield(triple, currentContext)
        else
          results << triple
        end
      end
      
      results.uniq
    end
    
    # Check to see if this store contains the specified triple
    #
    # @param [Triple] triple
    # @param [Graph] context (nil)
    # @return [Boolean]
    def contains?(triple, context = nil)
      #puts "contains? #{triple}"
      object = triple.object
      if object.is_a?(Literal)
        triple = Triple.new(triple.subject, triple.predicate, nil)
        triples(triple, context) do |t, cg|
          return true if t.object == object
        end
        false
      else
        !triples(triple, context).empty?
      end
    end
    
    # Number of statements in the store.
    # @param [Graph] context (nil)
    # @return [Integer]
    def size(context = nil)
      parameters = []
      quotedContext = assertedContext = typeContext = literalContext = nil

      clauseParts = self.buildContextClause(context,quoted_table)
      if clauseParts
        quotedContext = clauseParts.shift
        parameters += clauseParts
      end

      clauseParts = self.buildContextClause(context,asserted_table)
      if clauseParts
        assertedContext = clauseParts.shift
        parameters += clauseParts
      end

      clauseParts = self.buildContextClause(context,asserted_type_table)
      if clauseParts
        typeContext = clauseParts.shift
        parameters += clauseParts
      end

      clauseParts = self.buildContextClause(context,literal_table)
      if clauseParts
        literalContext = clauseParts.shift
        parameters += clauseParts
      end

      if context
        selects = [
          [
            asserted_type_table,
            'typeTable',
            typeContext ? 'where ' + typeContext : '',
            ASSERTED_TYPE_PARTITION
          ],
          [
            quoted_table,
            'quoted',
            quotedContext ? 'where ' + quotedContext : '',
            QUOTED_PARTITION
          ],
          [
            asserted_table,
            'asserted',
            assertedContext ? 'where ' + assertedContext : '',
            ASSERTED_NON_TYPE_PARTITION
          ],
          [
            literal_table,
            'literal',
            literalContext ? 'where ' + literalContext : '',
            ASSERTED_LITERAL_PARTITION
          ],
        ]
        q=unionSELECT(selects, :distinct => true, :select_type => COUNT_SELECT)
      else
        selects = [
          [
            asserted_type_table,
            'typeTable',
            typeContext ? 'where ' + typeContext : '',
            ASSERTED_TYPE_PARTITION
          ],
          [
            asserted_table,
            'asserted',
            assertedContext ? 'where ' + assertedContext : '',
            ASSERTED_NON_TYPE_PARTITION
          ],
          [
            literal_table,
            'literal',
            literalContext ? 'where ' + literalContext : '',
            ASSERTED_LITERAL_PARTITION
          ],
        ]
        q=unionSELECT(selects, :select_type => COUNT_SELECT)
      end

      count = 0
      executeSQL(self._normalizeSQLCmd(q), parameters) do |row|
        count += row[0].to_i
      end
      count
    end

    # Contexts containing the triple (no matching), or total number of contexts in store
    # @param [Triple] triple (nil) Containing the triple/pattern if not nil
    # @return [Array<Graph>]
    def contexts(triple = nil)
      parameters = []
      
      if triple
        subject, predicate, object = triple.subject, triple.predicate, triple.object
        if predicate == RDF_TYPE
          # select from asserted rdf:type partition and quoted table (if a context is specified)
          clauseString, *params = self.buildClause('typeTable',triple,nil, true)
          parameters += params
          selects = [
            [
              asserted_type_table,
              'typeTable',
              clauseString,
              ASSERTED_TYPE_PARTITION
            ],
          ]
        #elsif predicate.is_a?(REGEXTerm) && predicate.compiledExpr.match(RDF_TYPE) || predicate.nil?
        elsif predicate.nil?
          # Select from quoted partition (if context is specified), literal partition if (obj is Literal or None) and asserted non rdf:type partition (if obj is URIRef or None)
          clauseString, *params = self.buildClause('typeTable',Triple.new(subject, RDF_TYPE, object),nil, true)
          parameters += params
          selects = [
            [
              asserted_type_table,
              'typeTable',
              clauseString,
              ASSERTED_TYPE_PARTITION
            ],
          ]

          if !STRONGLY_TYPED_TERMS || triple.object.is_a?(Literal) || triple.object.nil?
            clauseString, *params = self.buildClause('literal',triple)
            parameters += params
            selects += [
              [
                literal_table,
                'literal',
                clauseString,
                ASSERTED_LITERAL_PARTITION
              ]
            ]
          end
          if !object.is_a?(Literal) || object.nil?
            clauseString, *params = self.buildClause('asserted',triple)
            parameters += params
            selects += [
              [
                asserted_table,
                'asserted',
                clauseString,
                ASSERTED_NON_TYPE_PARTITION
              ]
            ]
          end
        elsif predicate
          # select from asserted non rdf:type partition (optionally), quoted partition (if context is speciied), and literal partition (optionally)
          selects = []
          if !STRONGLY_TYPED_TERMS || object.is_a?(Literal) || object.nil?
            clauseString, *params = self.buildClause('literal',triple)
            parameters += params
            selects += [
              [
                literal_table,
                'literal',
                clauseString,
                ASSERTED_LITERAL_PARTITION
              ]
            ]
          end
          if !object.is_a?(Literal) || object.nil?
            clauseString, *params = self.buildClause('asserted',triple)
            parameters += params
            selects += [
              [
                asserted_table,
                'asserted',
                clauseString,
                ASSERTED_NON_TYPE_PARTITION
              ]
            ]
          end
        end

        clauseString, *params = self.buildClause('quoted',triple)
        parameters += params
        selects += [
          [
            quoted_table,
            'quoted',
            clauseString,
            QUOTED_PARTITION
          ]
        ]
      else
        selects = [
          [
            asserted_type_table,
            'typeTable',
            '',
            ASSERTED_TYPE_PARTITION
          ],
          [
            quoted_table,
            'quoted',
            '',
            QUOTED_PARTITION
          ],
          [
            asserted_table,
            'asserted',
            '',
            ASSERTED_NON_TYPE_PARTITION
          ],
          [
            literal_table,
            'literal',
            '',
            ASSERTED_LITERAL_PARTITION
          ],
        ]
      end

      q=unionSELECT(selects, :distinct => true, :select_type => CONTEXT_SELECT)
      executeSQL(_normalizeSQLCmd(q), parameters).map do |row|
        id, termComb = row

        termCombString = REVERSE_TERM_COMBINATIONS[termComb.to_i]
        subjTerm, predTerm, objTerm, ctxTerm = termCombString.scan(/./)

        graphKlass, idKlass = constructGraph(ctxTerm)
        [graphKlass, idKlass.new(id)]
      end.uniq.map do |gi|
        graphKlass, id = gi
        graphKlass.new(:store => self, :identifier => id)
      end
    end
    
    # Namespace persistence interface implementation
    #
    # Bind namespace to store, returns bound namespace
    #
    # @param [Nameespace] namespace the namespace to bind
    # @return [Namespace] The newly bound or pre-existing namespace.
    def bind(namespace)
      # Remove existing bindings for the same URI
      executeSQL("DELETE FROM #{namespace_binds} WHERE prefix=?", namespace.prefix.to_s)
      executeSQL("INSERT INTO #{namespace_binds} VALUES (?, ?)", namespace.prefix.to_s, namespace.uri.to_s)
      # May throw exception, should be handled in driver-specific class

      @namespaceCache ||= {}
      @namespaceUriCache ||= {}
      @nsbinding = nil
      @uri_binding = nil
      @namespaceCache[namespace.prefix] = namespace
      @namespaceUriCache[namespace.uri.to_s] = namespace.prefix
      namespace
    end

    # Namespace for prefix
    # @param [String] prefix
    # @return [Namespace]
    def namespace(prefix)
      @namespaceCache ||= {}
      @namespaceUriCache ||= {}
      unless @namespaceCache.has_key?(prefix.to_s)
        @namespaceCache[prefix] = nil
        executeSQL("SELECT uri FROM #{namespace_binds} WHERE prefix=?", prefix.to_s) do |row|
          @namespaceCache[prefix.to_s] = Namespace.new(row[0], prefix.to_s)
          @namespaceUriCache[row[0].to_s] = prefix.to_s
        end
      end
      @namespaceCache[prefix.to_s]
    end
    
    # Prefix for namespace
    # @param [Namespace] namespcae
    # @return [String]
    def prefix(namespace)
      uri = namespace.is_a?(Namespace) ? namespace.uri.to_s : namespace

      @namespaceCache ||= {}
      @namespaceUriCache ||= {}
      unless @namespaceUriCache.has_key?(uri.to_s)
        @namespaceUriCache[uri.to_s] = nil
        executeSQL("SELECT prefix FROM #{namespace_binds} WHERE uri=?", uri) do |row|
          @namespaceUriCache[uri.to_s] = row[0]
        end
      end
      @namespaceUriCache[uri.to_s]
    end

    # Hash of prefix => Namespace bindings
    # @return [Hash{String => Namespace}]
    def nsbinding
      unless @nsbinding.is_a?(Hash)
        @nsbinding = {}
        @uri_binding = {}
        executeSQL("SELECT prefix, uri FROM #{namespace_binds}") do |row|
          prefix, uri = row
          namespace = Namespace.new(uri, prefix)
          @nsbinding[prefix] = namespace
          # Over-write an empty prefix
          @uri_binding[uri] = namespace unless prefix.to_s.empty?
          @uri_binding[uri] ||= namespace
        end
        @nsbinding
      end
      @nsbinding
    end
    
    # Hash of uri => Namespace bindings
    # @return [Hash{URIRef => Namespace}]
    def uri_binding
      nsbinding
      @uri_binding
    end
    
    # Transactional interfaces
    def commit; @db.commit; end

    def rollback; @db.rollback; end

    protected
    def quoted_table; "#{@internedId}_quoted_statements"; end
    def asserted_table; "#{@internedId}_asserted_statements"; end
    def asserted_type_table; "#{@internedId}_type_statements"; end
    def literal_table; "#{@internedId}_literal_statements"; end
    def namespace_binds; "#{@internedId}_namespace_binds"; end
    
    def remove_context(identifier)
      executeSQL("SET AUTOCOMMIT=0") if @autocommit_default
      
      %w(quoted asserted type literal)
      [quoted_table,asserted_table,asserted_type_table,literal_table].each do |table|
        clauseString, *params = self.buildContextClause(identifier,table)
        executeSQL(
            _normalizeSQLCmd("DELETE from #{table} where #{clauseString}"),
            params
        )
      end
    end
    
    # This takes the query string and parameters and (depending on the SQL implementation) either fill in
    # the parameter in-place or pass it on to the DB impl (if it supports this).
    # The default (here) is to fill the parameters in-place surrounding each param with quote characters
    #
    # Yields each row
    def executeSQL(qStr, *params, &block)
      @db.execute(qStr, *params, &block)
    end
    
    # Normalize a SQL command before executing it.  Commence unicode black magic
    def _normalizeSQLCmd(cmd)
      cmd # XXX
    end
    
    #T akes a term and 'normalizes' it.
    # Literals are escaped, Graphs are replaced with just their identifiers
    def normalizeTerm(term)
      case term
      when Graph    then normalizeTerm(term.identifier)
      when Literal  then term.to_s.rdf_escape
      when URIRef   then term.to_s.rdf_escape
      when BNode    then term.to_s
      else               term
      end
    end
    
    # Builds an insert command for a type table
    # Returns string and list of parameters
    def buildTypeSQLCommand(member,klass,context)
      [
        "INSERT INTO #{asserted_type_table} VALUES (?, ?, ?, ?)",
        normalizeTerm(member),
        normalizeTerm(klass),
        normalizeTerm(context),
        type2TermCombination(member, klass, context)
      ]
    end
    
    # Builds an insert command for literal triples (statements where the object is a Literal)
    # Returns string and list of parameters
    def buildLiteralTripleSQLCommand(triple,context)
      triplePattern = statement2TermCombination(triple,context)
      [
        "INSERT INTO #{literal_table} VALUES (?, ?, ?, ?, ?,?,?)",
        normalizeTerm(triple.subject),
        normalizeTerm(triple.predicate),
        normalizeTerm(triple.object),
        normalizeTerm(context),
        triplePattern,
        (triple.object.is_a?(Literal) ? triple.object.lang : NULL),
        (triple.object.is_a?(Literal) ? triple.object.encoding.value.to_s : NULL),
      ]
    end
    
    # Builds an insert command for regular triple table
    def buildTripleSQLCommand(triple,context,quoted)
      stmt_table = quoted ? quoted_table : asserted_table
      triplePattern = statement2TermCombination(triple,context)

      if quoted
        [
          "INSERT INTO #{stmt_table} VALUES (?, ?, ?, ?, ?,?,?)",
          normalizeTerm(triple.subject),
          normalizeTerm(triple.predicate),
          normalizeTerm(triple.object),
          normalizeTerm(context),
          triplePattern,
          (triple.object.is_a?(Literal) ? triple.object.lang : NULL),
          (triple.object.is_a?(Literal) ? triple.object.encoding.value.to_s : NULL),
        ]
      else
        [
          "INSERT INTO #{stmt_table} VALUES (?, ?, ?, ?, ?)",
          normalizeTerm(triple.subject),
          normalizeTerm(triple.predicate),
          normalizeTerm(triple.object),
          normalizeTerm(context),
          triplePattern
        ]
      end
    end
    
    # Builds WHERE clauses for the supplied terms and, context
    def buildClause(tableName,triple,context=nil,typeTable=false)
      parameters=[]
      if typeTable
        rdf_type_memberClause = rdf_type_klassClause = rdf_type_contextClause = nil

        # Subject clause
        clauseParts = self.buildTypeMemberClause(self.normalizeTerm(triple.subject),tableName)
        if clauseParts
          rdf_type_memberClause = clauseParts.shift
          parameters += clauseParts
        end

        # Object clause
        clauseParts = self.buildTypeClassClause(self.normalizeTerm(triple.object),tableName)
        if clauseParts
          rdf_type_klassClause = clauseParts.shift
          parameters += clauseParts
        end

        # Context clause
        clauseParts = self.buildContextClause(context,tableName)
        if clauseParts
          rdf_type_contextClause = clauseParts.shift
          parameters += clauseParts
        end

        clauses = [rdf_type_memberClause,rdf_type_klassClause,rdf_type_contextClause].compact
      else
        subjClause = predClause = objClause = contextClause = litDTypeClause = litLanguageClause = nil

        # Subject clause
        clauseParts = self.buildSubjClause(self.normalizeTerm(triple.subject),tableName)
        if clauseParts
          subjClause = clauseParts.shift
          parameters += clauseParts
        end

        # Predicate clause
        clauseParts = self.buildPredClause(self.normalizeTerm(triple.predicate),tableName)
        if clauseParts
          predClause = clauseParts.shift
          parameters += clauseParts
        end

        # Object clause
        clauseParts = self.buildObjClause(self.normalizeTerm(triple.object),tableName)
        if clauseParts
          objClause = clauseParts.shift
          parameters += clauseParts
        end

        # Context clause
        clauseParts = self.buildContextClause(context,tableName)
        if clauseParts
          contextClause = clauseParts.shift
          parameters += clauseParts
        end

        # Datatype clause
        clauseParts = self.buildLitDTypeClause(triple.object,tableName)
        if clauseParts
          litDTypeClause = clauseParts.shift
          parameters += clauseParts
        end

        # Language clause
        clauseParts = self.buildLitLanguageClause(triple.object,tableName)
        if clauseParts
          litLanguageClause = clauseParts.shift
          parameters += clauseParts
        end
        
        clauses = [subjClause,predClause,objClause,contextClause,litDTypeClause,litLanguageClause].compact
      end

      clauseString = clauses.join(' and ')
      clauseString = "WHERE #{clauseString}" unless clauseString.empty?
      
      [clauseString] + parameters
    end

    def buildLitDTypeClause(obj,tableName)
      ["#{tableName}.objDatatype='#{obj.encoding.value}'"] if obj.is_a?(Literal) && obj.encoding
    end
    
    def buildLitLanguageClause(obj,tableName)
      ["#{tableName}.objLanguage='#{obj.lang}'"] if obj.is_a?(Literal) && obj.lang
    end

    # Stubs for Clause Functions that are overridden by specific implementations (MySQL vs SQLite for instance)
    def buildSubjClause(subject,tableName); end
    def buildPredClause(predicate,tableName); end
    def buildObjClause(obj,tableName); end
    def buildContextClause(context,tableName); end
    def buildTypeMemberClause(subject,tableName); end
    def buildTypeClassClause(obj,tableName); end

    # Helper function for executing EXPLAIN on all dispatched SQL statements - for the pupose of analyzing
    # index usage
    def queryAnalysis(query)
    end
    
    # Helper function for building union all select statement
    # @param [Array] select_components:: list of [table_name, table_alias, table_type, where_clause]
    # @param [Hash] options:: Options
    # <em>options[:distinct]</em>:: true or false
    # <em>options[:select_type]</em>:: Defaults to TRIPLE_SELECT
    def unionSELECT(selectComponents, options = {})
      selectType = options[:select_type] || TRIPLE_SELECT
      selects = []
      
      selectComponents.each do |sc|
        tableName, tableAlias, whereClause, tableType = sc

        case
        when selectType == COUNT_SELECT
          selectString = "select count(*)"
          tableSource = " from #{tableName} "
        when selectType == CONTEXT_SELECT
          selectString = "select #{tableAlias}.context, " + 
                                "#{tableAlias}.termComb as termComb "
          tableSource = " from #{tableName} as #{tableAlias} "
        when FULL_TRIPLE_PARTITIONS.include?(tableType)
          selectString = "select *"
          tableSource = " from #{tableName} as #{tableAlias} "
        when tableType == ASSERTED_TYPE_PARTITION
          selectString = "select #{tableAlias}.member as subject, " +
                                "\"#{RDF_TYPE}\" as predicate, " + 
                                "#{tableAlias}.klass as object, " + 
                                "#{tableAlias}.context as context, " + 
                                "#{tableAlias}.termComb as termComb, " + 
                                "NULL as objLanguage, " + 
                                "NULL as objDatatype"
          tableSource = " from #{tableName} as #{tableAlias} "
        when tableType == ASSERTED_NON_TYPE_PARTITION
          selectString = "select *, NULL as objLanguage, NULL as objDatatype"
          tableSource = " from #{tableName} as #{tableAlias} "
        else
          raise StoreException, "unionSELECT failed to find template: selectType = #{selectType}, tableType = #{tableType}"
        end
        
        selects << "#{selectString}#{tableSource}#{whereClause}"
      end
      
      orderStmt = selectType == TRIPLE_SELECT ? " order by subject, predicate, object" : ""
      
      selects.join(options[:distinct] ? " union all ": " union ") + orderStmt
    end
    
    # Takes a tuple which represents an entry in a result set and
    # converts it to a tuple of terms using the termComb integer
    # to interpret how to instanciate each term
    # tupleRt is an array containing one or more of:
    # - subject
    # - predicate
    # - obj
    # - rtContext
    # - termComb
    # - objLanguage
    # - objDatatype
    def extractTriple(tupleRt, hardCodedContext = nil)
      subject, predicate, obj, rtContext, termComb, objLanguage, objDatatype = tupleRt

      raise StoreException, "extractTriple: unknow termComb: '#{termComb}'" unless REVERSE_TERM_COMBINATIONS.has_key?(termComb.to_i)

      context = rtContext || hardCodedContext
      termCombString = REVERSE_TERM_COMBINATIONS[termComb.to_i]
      subjTerm, predTerm, objTerm, ctxTerm = termCombString.scan(/./)
      
      s = createTerm(subject, subjTerm)
      p = createTerm(predicate, predTerm)
      o = createTerm(obj, objTerm, objLanguage, objDatatype)

      graphKlass, idKlass = constructGraph(ctxTerm)
      return [Triple.new(s, p, o), graphKlass, idKlass, context]
    end
    
    # Takes a term value, and term type
    # and Creates a term object.  QuotedGraphs are instantiated differently
    def createTerm(termString,termType,objLanguage=nil,objDatatype=nil)
      #puts "createTerm(#{termString}, #{termType}, ...)" if ::RdfContext::debug?
      case termType
      when "L"
        @literalCache[[termString, objLanguage, objDatatype]] ||= Literal.n3_encoded(termString, objLanguage, objDatatype)
      when "F"
        @otherCache[[termType, termString]] ||= QuotedGraph(:identifier => URIRef(termString), :store => self)
      when "B"
        @bnodeCache[termString] ||= begin
          bn = BNode.new
          bn.identifier = termString
          bn
        end
      when "U"
        @uriCache[termString] || URIRef.new(termString)
#      when "V"
      else
        raise StoreException.new("Unknown termType: #{termType}")
      end
    end
  end
end