// file      : odb/relational/mssql/source.cxx
// copyright : Copyright (c) 2009-2015 Code Synthesis Tools CC
// license   : GNU GPL v3; see accompanying LICENSE file

#include <odb/relational/source.hxx>

#include <odb/relational/mssql/common.hxx>
#include <odb/relational/mssql/context.hxx>

using namespace std;

namespace relational
{
  namespace mssql
  {
    namespace source
    {
      namespace relational = relational::source;

      //
      //
      struct query_parameters: relational::query_parameters
      {
        query_parameters (base const& x): base (x) {}

        virtual string
        auto_id ()
        {
          return "";
        }
      };
      entry<query_parameters> query_parameters_;

      //
      //
      struct object_columns: relational::object_columns, context
      {
        object_columns (base const& x)
          : base (x), rowversion_ (false), column_count_ (0) {}

        virtual bool
        column (semantics::data_member& m,
                string const& table,
                string const& column)
        {
          // Don't add a column for auto id in the INSERT statement.
          //
          if (sk_ == statement_insert &&
              key_prefix_.empty () &&
              context::id (m) && auto_(m)) // Only simple id can be auto.
            return false;

          // Don't update the ROWVERSION column explicitly.
          //
          if (sk_ == statement_update)
          {
            sql_type t (parse_sql_type (column_type (), m));
            if (t.type == sql_type::ROWVERSION)
            {
              rowversion_ = true;
              return false;
            }
          }

          bool r (base::column (m, table, column));

          // Count the number of columns in the UPDATE statement, but
          // excluding soft-deleted.
          //
          if (sk_ == statement_update && r && !deleted (member_path_))
            column_count_++;

          return r;
        }

        virtual void
        traverse_post (semantics::nameable& n)
        {
          if (rowversion_ && column_count_ == 0)
          {
            location l (n.location ());
            error (l) << "ROWVERSION in an object without any readwrite "
              "data members" << endl;
            error (l) << "UPDATE statement will be empty" << endl;
            throw operation_failed ();
          }
        }

      private:
        bool rowversion_;
        size_t column_count_;
      };
      entry<object_columns> object_columns_;

      //
      //
      struct persist_statement_params: relational::persist_statement_params,
                                       context
      {
        persist_statement_params (base const& x): base (x) {}

        virtual string
        version_value (semantics::data_member& m)
        {
          sql_type t (parse_sql_type (column_type (), m));
          return t.type == sql_type::ROWVERSION ? "DEFAULT" : "1";
        }
      };
      entry<persist_statement_params> persist_statement_params_;

      //
      // bind
      //

      static const char* integer_buffer_types[] =
      {
        "mssql::bind::bit",
        "mssql::bind::tinyint",
        "mssql::bind::smallint",
        "mssql::bind::int_",
        "mssql::bind::bigint"
      };

      struct bind_member: relational::bind_member_impl<sql_type>,
                          member_base
      {
        bind_member (base const& x)
            : member_base::base (x),      // virtual base
              member_base::base_impl (x), // virtual base
              base_impl (x),
              member_base (x)
        {
        }

        virtual void
        traverse_integer (member_info& mi)
        {
          os << b << ".type = " <<
            integer_buffer_types[mi.st->type - sql_type::BIT] << ";"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }

        virtual void
        traverse_decimal (member_info& mi)
        {
          os << b << ".type = mssql::bind::decimal;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode precision (p) and scale (s) as (p * 100 + s).
            //
             << b << ".capacity = " << mi.st->prec * 100 + mi.st->scale << ";";
        }

        virtual void
        traverse_smallmoney (member_info& mi)
        {
          os << b << ".type = mssql::bind::smallmoney;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }

        virtual void
        traverse_money (member_info& mi)
        {
          os << b << ".type = mssql::bind::money;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }

        virtual void
        traverse_float4 (member_info& mi)
        {
          os << b << ".type = mssql::bind::float4;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
             << b << ".capacity = " << mi.st->prec << ";";
        }

        virtual void
        traverse_float8 (member_info& mi)
        {
          os << b << ".type = mssql::bind::float8;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
             << b << ".capacity = " << mi.st->prec << ";";
        }

        virtual void
        traverse_string (member_info& mi)
        {
          os << b << ".type = mssql::bind::string;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
             << b << ".capacity = static_cast<SQLLEN> (sizeof (" <<
            arg << "." << mi.var << "value));";
        }

        virtual void
        traverse_long_string (member_info& mi)
        {
          os << b << ".type = mssql::bind::long_string;"
             << b << ".buffer = &" << arg << "." << mi.var << "callback;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode the column size with 0 indicating unlimited.
            //
             << b << ".capacity = " << mi.st->prec << ";";
        }

        virtual void
        traverse_nstring (member_info& mi)
        {
          os << b << ".type = mssql::bind::nstring;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
             << b << ".capacity = static_cast<SQLLEN> (sizeof (" <<
            arg << "." << mi.var << "value));";
        }

        virtual void
        traverse_long_nstring (member_info& mi)
        {
          os << b << ".type = mssql::bind::long_nstring;"
             << b << ".buffer = &" << arg << "." << mi.var << "callback;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode the column size (in bytes) with 0 indicating unlimited.
            //
             << b << ".capacity = " << mi.st->prec * 2 << ";";
        }

        virtual void
        traverse_binary (member_info& mi)
        {
          os << b << ".type = mssql::bind::binary;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
             << b << ".capacity = static_cast<SQLLEN> (sizeof (" <<
            arg << "." << mi.var << "value));";
        }

        virtual void
        traverse_long_binary (member_info& mi)
        {
          os << b << ".type = mssql::bind::long_binary;"
             << b << ".buffer = &" << arg << "." << mi.var << "callback;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode the column size with 0 indicating unlimited.
            //
             << b << ".capacity = " << mi.st->prec << ";";
        }

        virtual void
        traverse_date (member_info& mi)
        {
          os << b << ".type = mssql::bind::date;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }

        virtual void
        traverse_time (member_info& mi)
        {
          os << b << ".type = mssql::bind::time;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode fractional seconds (scale).
            //
             << b << ".capacity = " << mi.st->scale << ";";
        }

        virtual void
        traverse_datetime (member_info& mi)
        {
          unsigned short scale (0);

          switch (mi.st->type)
          {
          case sql_type::DATETIME:
            {
              // Looks like it is 3 (rounded to 0.000, 0.003, or 0.007).
              //
              scale = 3;
              break;
            }
          case sql_type::DATETIME2:
            {
              scale = mi.st->scale;
              break;
            }
          case sql_type::SMALLDATETIME:
            {
              // No seconds in SMALLDATATIME. Encode it a special precision
              // value (8).
              //
              scale = 8;
              break;
            }
          default:
            {
              assert (false);
              break;
            }
          }

          os << b << ".type = mssql::bind::datetime;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode fractional seconds (scale).
            //
             << b << ".capacity = " << scale << ";";
        }

        virtual void
        traverse_datetimeoffset (member_info& mi)
        {
          os << b << ".type = mssql::bind::datetimeoffset;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"
            // Encode fractional seconds (scale).
            //
             << b << ".capacity = " << mi.st->scale << ";";
        }

        virtual void
        traverse_uniqueidentifier (member_info& mi)
        {
          os << b << ".type = mssql::bind::uniqueidentifier;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }

        virtual void
        traverse_rowversion (member_info& mi)
        {
          os << b << ".type = mssql::bind::rowversion;"
             << b << ".buffer = &" << arg << "." << mi.var << "value;"
             << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;";
        }
      };
      entry<bind_member> bind_member_;

      //
      // init image
      //

      struct init_image_member: relational::init_image_member_impl<sql_type>,
                                member_base
      {
        init_image_member (base const& x)
            : member_base::base (x),      // virtual base
              member_base::base_impl (x), // virtual base
              base_impl (x),
              member_base (x)
        {
        }

        virtual void
        set_null (member_info& mi)
        {
          os << "i." << mi.var << "size_ind = SQL_NULL_DATA;";
        }

        virtual void
        check_accessor (member_info& mi, member_access& ma)
        {
          // We cannot use accessors that return by-value for long data
          // members.
          //
          if (long_data (*mi.st) && ma.by_value)
          {
            error (ma.loc) << "accessor returning a value cannot be used "
                           << "for a data member of SQL Server long data "
                           << "type" << endl;
            info (ma.loc) << "accessor returning a const reference is required"
                          << endl;
            info (mi.m.location ()) << "data member is defined here" << endl;
            throw operation_failed ();
          }
        }

        virtual void
        traverse_integer (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_decimal (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_smallmoney (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 4;";
        }

        virtual void
        traverse_money (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;";
        }

        virtual void
        traverse_float4 (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_float8 (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_string (member_info& mi)
        {
          os << "std::size_t size (0);"
             << traits << "::set_image (" << endl
             << "i." << mi.var << "value," << endl
            // Don't mention the extra character for the null-terminator.
             << "sizeof (i." << mi.var << "value) - 1," << endl
             << "size," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind =" << endl
             << "  is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);";
        }

        virtual void
        traverse_long_string (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "callback.callback.param," << endl
             << "i." << mi.var << "callback.context.param," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind = is_null ? " <<
            "SQL_NULL_DATA : SQL_DATA_AT_EXEC;";
        }

        virtual void
        traverse_nstring (member_info& mi)
        {
          os << "std::size_t size (0);"
             << traits << "::set_image (" << endl
             << "i." << mi.var << "value," << endl
            // Don't mention the extra character for the null-terminator.
             << "sizeof (i." << mi.var << "value) / 2 - 1," << endl
             << "size," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind =" << endl
             << "  is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);";
        }

        virtual void
        traverse_long_nstring (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "callback.callback.param," << endl
             << "i." << mi.var << "callback.context.param," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind = is_null ? " <<
            "SQL_NULL_DATA : SQL_DATA_AT_EXEC;";
        }

        virtual void
        traverse_binary (member_info& mi)
        {
          os << "std::size_t size (0);"
             << traits << "::set_image (" << endl
             << "i." << mi.var << "value," << endl
             << "sizeof (i." << mi.var << "value)," << endl
             << "size," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind =" << endl
             << "  is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);";
        }

        virtual void
        traverse_long_binary (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "callback.callback.param," << endl
             << "i." << mi.var << "callback.context.param," << endl
             << "is_null," << endl
             << member << ");"
             << "i." << mi.var << "size_ind = is_null ? " <<
            "SQL_NULL_DATA : SQL_DATA_AT_EXEC;";
        }

        virtual void
        traverse_date (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_time (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, " << mi.st->scale << ", " <<
            "is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null" << endl
             << "  ? SQL_NULL_DATA" << endl
             << "  : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));";
        }

        virtual void
        traverse_datetime (member_info& mi)
        {
          // The same code as in bind.
          //
          unsigned short scale (0);

          switch (mi.st->type)
          {
          case sql_type::DATETIME:
            {
              scale = 3;
              break;
            }
          case sql_type::DATETIME2:
            {
              scale = mi.st->scale;
              break;
            }
          case sql_type::SMALLDATETIME:
            {
              scale = 8;
              break;
            }
          default:
            {
              assert (false);
              break;
            }
          }

          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, " << scale << ", " <<
            "is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_datetimeoffset (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, " << mi.st->scale << ", " <<
            "is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null" << endl
             << "  ? SQL_NULL_DATA" << endl
             << "  : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));";
        }

        virtual void
        traverse_uniqueidentifier (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;";
        }

        virtual void
        traverse_rowversion (member_info& mi)
        {
          os << traits << "::set_image (" << endl
             << "i." << mi.var << "value, is_null, " << member << ");"
             << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;";
        }
      };
      entry<init_image_member> init_image_member_;

      //
      // init value
      //

      struct init_value_member: relational::init_value_member_impl<sql_type>,
                                member_base
      {
        init_value_member (base const& x)
            : member_base::base (x),      // virtual base
              member_base::base_impl (x), // virtual base
              base_impl (x),
              member_base (x)
        {
        }

        virtual void
        get_null (string const& var) const
        {
          os << "i." << var << "size_ind == SQL_NULL_DATA";
        }

        virtual void
        check_modifier (member_info& mi, member_access& ma)
        {
          // We cannot use by-value modifier for long data members.
          //
          if (long_data (*mi.st) && ma.placeholder ())
          {
            error (ma.loc) << "modifier accepting a value cannot be used "
                           << "for a data member of SQL Server long data "
                           << "type" << endl;
            info (ma.loc) << "modifier returning a non-const reference is "
                          << "required" << endl;
            info (mi.m.location ()) << "data member is defined here" << endl;
            throw operation_failed ();
          }
        }

        virtual void
        traverse_integer (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_decimal (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_smallmoney (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_money (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_float4 (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_float8 (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_string (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_long_string (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "callback.callback.result," << endl
             << "i." << mi.var << "callback.context.result);"
             << endl;
        }

        virtual void
        traverse_nstring (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "static_cast<std::size_t> (" <<
            "i." << mi.var << "size_ind / 2)," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_long_nstring (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "callback.callback.result," << endl
             << "i." << mi.var << "callback.context.result);"
             << endl;
        }

        virtual void
        traverse_binary (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_long_binary (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "callback.callback.result," << endl
             << "i." << mi.var << "callback.context.result);"
             << endl;
        }

        virtual void
        traverse_date (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_time (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_datetime (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_datetimeoffset (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_uniqueidentifier (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }

        virtual void
        traverse_rowversion (member_info& mi)
        {
          os << traits << "::set_value (" << endl
             << member << "," << endl
             << "i." << mi.var << "value," << endl
             << "i." << mi.var << "size_ind == SQL_NULL_DATA);"
             << endl;
        }
      };
      entry<init_value_member> init_value_member_;

      struct statement_columns_common: context
      {
        void
        process (relational::statement_columns& cols,
                 statement_kind sk,
                 bool dynamic)
        {
          using relational::statement_columns;

          // Long data columns must come last in the SELECT statement. If
          // this statement is going to be processed at runtime, then this
          // will be taken care of then.
          //
          if (sk != statement_select || dynamic)
            return;

          // Go over the columns list while keeping track of how many
          // columns we have examined. If the current column is long data,
          // then move it to the back. Stop once we have examined all the
          // columns.
          //
          size_t n (cols.size ());
          for (statement_columns::iterator i (cols.begin ()); n != 0; --n)
          {
            if (long_data (parse_sql_type (i->type, *i->member)))
            {
              cols.push_back (*i);
              i = cols.erase (i);
            }
            else
              ++i;
          }
        }
      };

      struct container_traits: relational::container_traits,
                               statement_columns_common
      {
        container_traits (base const& x): base (x) {}

        virtual void
        cache_result (string const&)
        {
          // Caching is not necessary since with MARS enabled SQL Server
          // can execute several interleaving statements.
          //
        }

        virtual void
        init_value_extra ()
        {
          os << "sts.select_statement ().stream_result ();"
             << endl;
        }

        virtual void
        process_statement_columns (relational::statement_columns& cols,
                                   statement_kind sk,
                                   bool dynamic)
        {
          statement_columns_common::process (cols, sk, dynamic);
        }
      };
      entry<container_traits> container_traits_;

      struct section_traits: relational::section_traits,
                             statement_columns_common
      {
        section_traits (base const& x): base (x) {}

        virtual void
        init_value_extra ()
        {
          os << "st.stream_result ();";
        }

        virtual void
        process_statement_columns (relational::statement_columns& cols,
                                   statement_kind sk,
                                   bool dynamic)
        {
          statement_columns_common::process (cols, sk, dynamic);
        }

        virtual string
        optimistic_version_increment (semantics::data_member& m)
        {
          sql_type t (parse_sql_type (column_type (m), m));
          return t.type != sql_type::ROWVERSION
            ? "1"
            : "version (sts.id_image ())";
        }

        virtual string
        update_statement_extra (user_section&)
        {
          string r;

          semantics::data_member* ver (optimistic (c_));

          if (ver == 0 ||
              parse_sql_type (column_type (*ver), *ver).type !=
              sql_type::ROWVERSION)
            return r;

          // ROWVERSION & SQL Server 2005 incompatibility is detected
          // in persist_statement_extra.
          //
          r = "OUTPUT INSERTED." +
            convert_from (column_qname (*ver, column_prefix ()), *ver);

          return r;
        }
      };
      entry<section_traits> section_traits_;

      struct class_: relational::class_, statement_columns_common
      {
        class_ (base const& x):
            base (x), init_version_value_member_id_image_ ("v", "version_") {}

        virtual void
        init_image_pre (type& c)
        {
          if (options.generate_query () &&
              !(composite (c) || (abstract (c) && !polymorphic (c))))
          {
            type* poly_root (polymorphic (c));
            bool poly_derived (poly_root != 0 && poly_root != &c);

            if (poly_derived)
              os << "{"
                 << "root_traits::image_type& ri (root_image (i));"
                 << endl;

            string i (poly_derived ? "ri" : "i");

            os << "if (" << i << ".change_callback_.callback != 0)" << endl
               << "(" << i << ".change_callback_.callback) (" <<
              i << ".change_callback_.context);";

            if (poly_derived)
              os << "}";
            else
              os << endl;
          }
        }

        virtual void
        init_value_extra ()
        {
          os << "st.stream_result ();";
        }

        virtual string
        persist_statement_extra (type& c,
                                 relational::query_parameters&,
                                 persist_position p)
        {
          string r;

          type* poly_root (polymorphic (c));
          bool poly_derived (poly_root != 0 && poly_root != &c);

          // If we are a derived type in a polymorphic hierarchy, then
          // auto id/version are handled by the root.
          //
          if (poly_derived)
            return r;

          // See if we have auto id or ROWVERSION version.
          //
          semantics::data_member* id (id_member (c));
          semantics::data_member* ver (optimistic (c));

          if (id != 0 && !auto_ (*id))
            id = 0;

          if (ver != 0)
          {
            sql_type t (parse_sql_type (column_type (*ver), *ver));
            if (t.type != sql_type::ROWVERSION)
              ver = 0;
          }

          if (id == 0 && ver == 0)
            return r;

          // SQL Server 2005 has a bug that causes it to fail on an
          // INSERT statement with the OUTPUT clause if data for one
          // of the inserted columns is supplied at execution (long
          // data). To work around this problem we use the less
          // efficient batch of INSERT and SELECT statements.
          //
          if (options.mssql_server_version () <= mssql_version (9, 0))
          {
            bool ld (false);

            if (c.count ("mssql-has-long-data"))
              ld = c.get<bool> ("mssql-has-long-data");
            else
            {
              has_long_data t (ld);
              t.traverse (c);
              c.set ("mssql-has-long-data", ld);
            }

            if (ld)
            {
              if (p == persist_after_values)
              {
                // SQL Server 2005 has no eqivalent of SCOPE_IDENTITY for
                // ROWVERSION.
                //
                if (ver != 0)
                {
                  error (c.location ()) << "in SQL Server 2005 ROWVERSION " <<
                    "value cannot be retrieved for a persistent class " <<
                    "containing long data" << endl;
                  throw operation_failed ();
                }

                // We also cannot support bulk INSERT.
                //
                if (c.count ("bulk-persist"))
                {
                  error (c.location ()) << "in SQL Server 2005 bulk " <<
                    "persist operation cannot be implemented for a " <<
                    "persistent class containing long data" << endl;
                  throw operation_failed ();
                }

                r = "; SELECT " + convert_from ("SCOPE_IDENTITY()", *id);
              }

              return r;
            }
          }

          if (p == persist_after_columns)
          {
            r = "OUTPUT ";

            // Top-level auto id column.
            //
            if (id != 0)
              r += "INSERTED." + convert_from (
                column_qname (*id, column_prefix ()), *id);

            // Top-level version column.
            //
            if (ver != 0)
            {
              if (id != 0)
                r += ',';

              r += "INSERTED." + convert_from (
                column_qname (*ver, column_prefix ()), *ver);
            }
          }

          return r;
        }

        virtual string
        update_statement_extra (type& c)
        {
          string r;

          type* poly_root (polymorphic (c));
          bool poly_derived (poly_root != 0 && poly_root != &c);

          // If we are a derived type in a polymorphic hierarchy, then
          // version is handled by the root.
          //
          if (poly_derived)
            return r;

          semantics::data_member* ver (optimistic (c));

          if (ver == 0 ||
              parse_sql_type (column_type (*ver), *ver).type !=
              sql_type::ROWVERSION)
            return r;

          // Long data & SQL Server 2005 incompatibility is detected
          // in persist_statement_extra.
          //
          r = "OUTPUT INSERTED." +
            convert_from (column_qname (*ver, column_prefix ()), *ver);

          return r;
        }

        virtual void
        process_statement_columns (relational::statement_columns& cols,
                                   statement_kind sk,
                                   bool dynamic)
        {
          statement_columns_common::process (cols, sk, dynamic);
        }

        virtual string
        optimistic_version_init (semantics::data_member& m, bool index)
        {
          sql_type t (parse_sql_type (column_type (m), m));
          return t.type != sql_type::ROWVERSION
            ? "1"
            : (index
               ? "version (sts.id_image (i))"
               : "version (sts.id_image ())");
        }

        virtual string
        optimistic_version_increment (semantics::data_member& m, bool index)
        {
          sql_type t (parse_sql_type (column_type (m), m));
          return t.type != sql_type::ROWVERSION
            ? "1"
            : (index
               ? "version (sts.id_image (i))"
               : "version (sts.id_image ())");
        }

        virtual bool
        optimistic_insert_bind_version (semantics::data_member& m)
        {
          sql_type t (parse_sql_type (column_type (m), m));
          return t.type == sql_type::ROWVERSION;
        }

        virtual void
        object_extra (type& c)
        {
          bool abst (abstract (c));

          type* poly_root (polymorphic (c));
          bool poly (poly_root != 0);
          bool poly_derived (poly && poly_root != &c);

          if (poly_derived || (abst && !poly))
            return;

          if (semantics::data_member* m = optimistic (c))
          {
            sql_type t (parse_sql_type (column_type (*m), *m));
            if (t.type == sql_type::ROWVERSION)
            {
              string const& type (class_fq_name (c));
              string traits ("access::object_traits_impl< " + type + ", id_" +
                             db.string () + " >");

              os << traits << "::version_type" << endl
                 << traits << "::" << endl
                 << "version (const id_image_type& i)"
                 << "{"
                 << "version_type v;";
              init_version_value_member_id_image_->traverse (*m);
              os << "return v;"
                 << "}";
            }
          }
        }

        virtual string
        from_trailer (type& c)
        {
          return c.get<view_query> ("query").for_update
            ? " WITH (UPDLOCK)"
            : "";
        }

        virtual string
        select_trailer (type&) {return "";}

      private:
        // Go via the dynamic creation to get access to the constructor.
        //
        instance<relational::init_value_member>
          init_version_value_member_id_image_;
      };
      entry<class_> class_entry_;
    }
  }
}
