[odb-users] composite ID containing an auto-incrementable field
    Sten Kultakangas 
    ratkaisut at gmail.com
       
    Tue Jul 28 02:47:21 EDT 2020
    
    
  
Hi Boris
I was able to produce code which inserts the record to the database,
however, i could not retrieve the inserted ID. I suspect
mssql::object_statements<object_type>::id_image() cannot be used with more
than one bound value. Can you confirm this? If that's the case, then the
following assignment cannot work:
obj.key = id (sts.id_image ());
So we would need to generate a totally different alternative to
access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::id()
Obviously we only need the inserted auto id, not the tenant_id, but i
assumed we can reuse the existing code which can be used to generate
access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::id()
Here's the code
  const char access::object_traits_impl< ::GenericSwitch::Extension,
id_mssql >::persist_statement[] =
  "INSERT INTO [GenericSwitch].[dbo].[Extension] "
  "([tenant_id], "
  "[extension], "
  "[name], "
  "[type_id], "
  "[timezone], "
  "[coverage_map_id], "
  "[office_hours_map_id], "
  "[office_holidays_map_id], "
  "[rec_days]) "
  "OUTPUT INSERTED.[id],INSERTED.[tenant_id] "
  "VALUES "
  "(?, ?, ?, ?, ?, ?, ?, ?, ?)";
void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  persist (database& db, object_type& obj)
  {
    ODB_POTENTIALLY_UNUSED (db);
    using namespace mssql;
    mssql::connection& conn (
      mssql::transaction::current ().connection ());
    statements_type& sts (
      conn.statement_cache ().find_object<object_type> ());
    callback (db,
              obj,
              callback_event::pre_persist);
    image_type& im (sts.image ());
    binding& imb (sts.insert_image_binding ());
    init (im, obj, statement_insert);
    if (im.version != sts.insert_image_version () ||
        imb.version == 0)
    {
      bind (imb.bind, im, statement_insert);
      sts.insert_image_version (im.version);
      imb.version++;
    }
     {
       id_image_type& i (sts.id_image ());
       binding& b (sts.id_image_binding ());
       if (i.version != sts.id_image_version () || b.version == 0)
       {
         bind (b.bind, i);
         sts.id_image_version (i.version);
         b.version++;
      }
    }
    insert_statement& st (sts.persist_statement ());
    if (!st.execute ())
      throw object_already_persistent ();
    obj.key = id (sts.id_image ()); // <--- problem here
    callback (db,
              obj,
              callback_event::post_persist);
  }
  access::object_traits_impl< ::GenericSwitch::Extension, id_mssql
>::id_type
  access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  id (const id_image_type& i)
  {
    mssql::database* db (0);
    ODB_POTENTIALLY_UNUSED (db);
    id_type id;
    {
      composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::init (
        id,
        i.id_value,
        db);
    }
    return id;
  }
  access::object_traits_impl< ::GenericSwitch::Extension, id_mssql
>::id_type
  access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  id (const image_type& i)
  {
    mssql::database* db (0);
    ODB_POTENTIALLY_UNUSED (db);
    id_type id;
    {
      composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::init (
        id,
        i.key_value,
        db);
    }
    return id;
  }
  void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  bind (mssql::bind* b,
        image_type& i,
        mssql::statement_kind sk)
  {
    ODB_POTENTIALLY_UNUSED (sk);
    using namespace mssql;
    std::size_t n (0);
    // key
    //
    if (sk == statement_insert) {
    b[n].type = mssql::bind::bigint;
    b[n].buffer = &i.key_value.tenant_id_value;
    b[n].size_ind = &i.key_value.tenant_id_size_ind;
    n++;
    }
    else if (sk != statement_update)
    {
      composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::bind (
        b + n, i.key_value, sk);
      n += 2UL;
    }
    // extension
    //
    b[n].type = mssql::bind::string;
    b[n].buffer = &i.extension_value;
    b[n].size_ind = &i.extension_size_ind;
    b[n].capacity = static_cast<SQLLEN> (sizeof (i.extension_value));
    n++;
    // name
    //
    b[n].type = mssql::bind::nstring;
    b[n].buffer = &i.name_value;
    b[n].size_ind = &i.name_size_ind;
    b[n].capacity = static_cast<SQLLEN> (sizeof (i.name_value));
    n++;
    // type_id
    //
    b[n].type = mssql::bind::bigint;
    b[n].buffer = &i.type_id_value;
    b[n].size_ind = &i.type_id_size_ind;
    n++;
    // timezone
    //
    b[n].type = mssql::bind::nstring;
    b[n].buffer = &i.timezone_value;
    b[n].size_ind = &i.timezone_size_ind;
    b[n].capacity = static_cast<SQLLEN> (sizeof (i.timezone_value));
    n++;
    // coverage_map_id
    //
    b[n].type = mssql::bind::bigint;
    b[n].buffer = &i.coverage_map_id_value;
    b[n].size_ind = &i.coverage_map_id_size_ind;
    n++;
    // office_hours_map_id
    //
    b[n].type = mssql::bind::bigint;
    b[n].buffer = &i.office_hours_map_id_value;
    b[n].size_ind = &i.office_hours_map_id_size_ind;
    n++;
    // office_holidays_map_id
    //
    b[n].type = mssql::bind::bigint;
    b[n].buffer = &i.office_holidays_map_id_value;
    b[n].size_ind = &i.office_holidays_map_id_size_ind;
    n++;
    // rec_days
    //
    b[n].type = mssql::bind::int_;
    b[n].buffer = &i.rec_days_value;
    b[n].size_ind = &i.rec_days_size_ind;
    n++;
  }
  void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  bind (mssql::bind* b, id_image_type& i)
  {
    std::size_t n (0);
    mssql::statement_kind sk (mssql::statement_select);
    composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::bind (
      b + n, i.id_value, sk);
  }
  void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  init (image_type& i,
        const object_type& o,
        mssql::statement_kind sk)
  {
    ODB_POTENTIALLY_UNUSED (i);
    ODB_POTENTIALLY_UNUSED (o);
    ODB_POTENTIALLY_UNUSED (sk);
    using namespace mssql;
    if (i.change_callback_.callback != 0)
      (i.change_callback_.callback) (i.change_callback_.context);
    // key
    //
    if (sk == statement_insert)
    {
      // tenant_id
      //
      {
        long long int const& v =
     o.key.tenant_id;
        bool is_null (false);
        mssql::value_traits<
       long long int,
       mssql::id_bigint >::set_image (
     i.key_value.tenant_id_value, is_null, v);
          i.key_value.tenant_id_size_ind = is_null ? SQL_NULL_DATA : 0;
      }
    }
    // extension
    //
    {
      ::std::string const& v =
        o.extension;
      bool is_null (false);
      std::size_t size (0);
      mssql::value_traits<
          ::std::string,
          mssql::id_string >::set_image (
        i.extension_value,
        sizeof (i.extension_value) - 1,
        size,
        is_null,
        v);
      i.extension_size_ind =
        is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);
    }
    // name
    //
    {
      ::std::string const& v =
        o.name;
      bool is_null (false);
      std::size_t size (0);
      mssql::value_traits<
          ::std::string,
          mssql::id_nstring >::set_image (
        i.name_value,
        sizeof (i.name_value) / 2 - 1,
        size,
        is_null,
        v);
      i.name_size_ind =
        is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);
    }
    // type_id
    //
    {
      ::GenericSwitch::ExtensionType const& v =
        o.type_id;
      bool is_null (false);
      mssql::value_traits<
          ::GenericSwitch::ExtensionType,
          mssql::id_bigint >::set_image (
        i.type_id_value, is_null, v);
      i.type_id_size_ind = is_null ? SQL_NULL_DATA : 0;
    }
    // timezone
    //
    {
      ::std::string const& v =
        o.timezone;
      bool is_null (false);
      std::size_t size (0);
      mssql::value_traits<
          ::std::string,
          mssql::id_nstring >::set_image (
        i.timezone_value,
        sizeof (i.timezone_value) / 2 - 1,
        size,
        is_null,
        v);
      i.timezone_size_ind =
        is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);
    }
    // coverage_map_id
    //
    {
      ::odb::nullable< long long int > const& v =
        o.coverage_map_id;
      bool is_null (true);
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_image (
        i.coverage_map_id_value, is_null, v);
      i.coverage_map_id_size_ind = is_null ? SQL_NULL_DATA : 0;
    }
    // office_hours_map_id
    //
    {
      ::odb::nullable< long long int > const& v =
        o.office_hours_map_id;
      bool is_null (true);
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_image (
        i.office_hours_map_id_value, is_null, v);
      i.office_hours_map_id_size_ind = is_null ? SQL_NULL_DATA : 0;
    }
    // office_holidays_map_id
    //
    {
      ::odb::nullable< long long int > const& v =
        o.office_holidays_map_id;
      bool is_null (true);
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_image (
        i.office_holidays_map_id_value, is_null, v);
      i.office_holidays_map_id_size_ind = is_null ? SQL_NULL_DATA : 0;
    }
    // rec_days
    //
    {
      ::odb::nullable< int > const& v =
        o.rec_days;
      bool is_null (true);
      mssql::value_traits<
          ::odb::nullable< int >,
          mssql::id_int >::set_image (
        i.rec_days_value, is_null, v);
      i.rec_days_size_ind = is_null ? SQL_NULL_DATA : 0;
    }
  }
  void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  init (object_type& o,
        const image_type& i,
        database* db)
  {
    ODB_POTENTIALLY_UNUSED (o);
    ODB_POTENTIALLY_UNUSED (i);
    ODB_POTENTIALLY_UNUSED (db);
    // key
    //
    {
      ::GenericSwitch::ObjectId& v =
        o.key;
      composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::init (
        v,
        i.key_value,
        db);
    }
    // extension
    //
    {
      ::std::string& v =
        o.extension;
      mssql::value_traits<
          ::std::string,
          mssql::id_string >::set_value (
        v,
        i.extension_value,
        static_cast<std::size_t> (i.extension_size_ind),
        i.extension_size_ind == SQL_NULL_DATA);
    }
    // name
    //
    {
      ::std::string& v =
        o.name;
      mssql::value_traits<
          ::std::string,
          mssql::id_nstring >::set_value (
        v,
        i.name_value,
        static_cast<std::size_t> (i.name_size_ind / 2),
        i.name_size_ind == SQL_NULL_DATA);
    }
    // type_id
    //
    {
      ::GenericSwitch::ExtensionType& v =
        o.type_id;
      mssql::value_traits<
          ::GenericSwitch::ExtensionType,
          mssql::id_bigint >::set_value (
        v,
        i.type_id_value,
        i.type_id_size_ind == SQL_NULL_DATA);
    }
    // timezone
    //
    {
      ::std::string& v =
        o.timezone;
      mssql::value_traits<
          ::std::string,
          mssql::id_nstring >::set_value (
        v,
        i.timezone_value,
        static_cast<std::size_t> (i.timezone_size_ind / 2),
        i.timezone_size_ind == SQL_NULL_DATA);
    }
    // coverage_map_id
    //
    {
      ::odb::nullable< long long int >& v =
        o.coverage_map_id;
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_value (
        v,
        i.coverage_map_id_value,
        i.coverage_map_id_size_ind == SQL_NULL_DATA);
    }
    // office_hours_map_id
    //
    {
      ::odb::nullable< long long int >& v =
        o.office_hours_map_id;
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_value (
        v,
        i.office_hours_map_id_value,
        i.office_hours_map_id_size_ind == SQL_NULL_DATA);
    }
    // office_holidays_map_id
    //
    {
      ::odb::nullable< long long int >& v =
        o.office_holidays_map_id;
      mssql::value_traits<
          ::odb::nullable< long long int >,
          mssql::id_bigint >::set_value (
        v,
        i.office_holidays_map_id_value,
        i.office_holidays_map_id_size_ind == SQL_NULL_DATA);
    }
    // rec_days
    //
    {
      ::odb::nullable< int >& v =
        o.rec_days;
      mssql::value_traits<
          ::odb::nullable< int >,
          mssql::id_int >::set_value (
        v,
        i.rec_days_value,
        i.rec_days_size_ind == SQL_NULL_DATA);
    }
  }
  void access::object_traits_impl< ::GenericSwitch::Extension, id_mssql >::
  init (id_image_type& i, const id_type& id)
  {
    mssql::statement_kind sk (mssql::statement_select);
    {
      composite_value_traits< ::GenericSwitch::ObjectId, id_mssql >::init (
        i.id_value,
        id,
        sk);
    }
  }
On Fri, Jul 24, 2020 at 2:17 PM Boris Kolpackov <boris at codesynthesis.com>
wrote:
> Sten Kultakangas <ratkaisut at gmail.com> writes:
>
> > Boris, I am going to make the necessary modifications to
> > odb/relational/source.cxx and odb/relational/mssql/source.cxx but i
> wonder
> > if you have some work-in-progress branch having the relevant
> modifications
> > that I can easily backport to the stable 2.5.0 branch ?
>
> No, I haven't looked into this.
>
>
> > If you haven't yet worked on this issue, then do you have instructions
> for
> > contributors on how to make a patch that can be incorporated into the
> > official version control branch ?
>
> You can either email the patch directly or create a pull request on your
> favorite Git hosting platform (GitHub, etc).
>
> Note that for this to be merged to master we would need the fix for all
> the databases as well as a test.
>
>
> > P.S. Actually tenant_id does not belong to the primary key, but i see no
> > other way to force odb to generate UPDATE statement having condition
> WHERE
> > tenant_id=X and id=X which is necessary for data integrity as our API
> does
> > not currently perform sanity-checks on the user-supplied ID but instead
> it
> > determines the tenant the API user belongs to.
>
> One somewhat admittedly hackish way to achieve this could be to "overlay"
> two classes over the same database table. One class would have the simple
> auto id to be used for persist and the other would have the composite id
> without auto to be used for update. You might also need to tweak the
> generated database schema depending on the database system used.
>
    
    
More information about the odb-users
mailing list