Forum OpenACS Q&A: Re: RFC: Avoid exposing object_ids in permanent URLs

Collapse
Posted by Christian Eva on
I agree very much with the proposal to have an external (permanent) id per object type. In my applications (non ACS, DB independent) I did choose to generate the ID's in one specific procedure for the whole application as not all DB's used do have sequences.

I have one table with all the last keys, which can also be initialised to logical start values and an increment value as well as a limit, where the numbering restarts.

This is some overhead but faster than the aggregate function solution and it leaves you in control. As it is in one place its functionality can be changed or improved without changing the logic in the different places of usage.

As it is a logical key (usually the table name) one can use any combination, i.e package-id+subsite-id etc).

This is just a simple approach and in now way the most sophisticated solution. It had to be done quickly and it does it's job since very well.

I insert the code here (out of my environment) just for illustration.

--  DBTYPE=pgs7, generated by zt_gen_sql at Tue Jan 14 16:29:16 GMT 2003
--  for PostgreSQL-7.x, to be executed by psql
--  (C) 1996/99 by Panorgan AG, CH-8820 Wädenswil

--  TABLE taf_idref - TAF ID Referenz Verwaltung

create table taf_idref
(
  i_idref    char(20) not null,    -- ID Referenz Name
  i_last    decimal(10) not null,  -- ID Letzter Wert
  i_start    decimal(10) not null,  -- ID Startwert
  i_incr    decimal(4) not null,  -- ID Inkrement
  i_limit    decimal(10) not null,  -- ID Limite
  primary key ( i_idref))

# ==========================================================
# y_db_getId -- generate uniqe id's for key fields
#
#          be aweare of calling the proc in a db transaction
#          otherwise we will commit the changes in here!
#
# USAGE:    idref      identifier for unique key (name of field)
#          tablename  idref table name (default taf_idref)
#          start      idref start value (default db definition)
#          incr        idref increment value (default db definition)
#          limit      idref limit value (default db definition)
#
#          start, incr, limit are only considered for a new idref!
#
# RETURN:  key        generated key value
# ==========================================================
proc y_db_getId {idref {table {taf_idref}} {start {}} \
                      {incr {}} {limit {}} } {
    global errorInfo errorCode DB_lib_priv Fields

    # make sure we have the table def
    y_table_def $table

    # lock the table to make sure we are the only one fiddling

    switch -- $DB_lib_priv(DB_TYPE) {
        adab    {
            set lock "lock (wait) table $table in exclusive mode"
        }
        orac    {
            set lock "lock table $table in exclusive mode"
        }
    }

    set ret [sql run $lock ]
    Debug $ret 9

    # read the last value
    set cmd "select i_last, i_start, i_incr, i_limit from $table
                    where i_idref = '$idref'"
    # open cursor
    if [catch {sql open $cmd} c] {
        Debug "Error in sql open: $c error=$errorCode"
        if {$errorCode < 0} {
            # return error
            return -code error
        }
    }
    Debug curs=$c 9

    if {![string length $c]} {

        # no record found, insert it
        if {[string length $start]} {
            set i_start $start
        } else {
            set i_start $Fields(i_start.value)
        }
        set i_last $i_start

        if {[string length $incr]} {
            set i_incr $incr
        } else {
            set i_incr $Fields(i_incr.value)
        }

        if {[string length $limit]} {
            set i_limit $limit
        } else {
            set i_limit $Fields(i_limit.value)
        }

        set ins "insert into $table (i_idref,i_last,i_start,i_incr,i_limit) \
                        values ('$idref',$i_last,$i_start,$i_incr,$i_limit)"

        set ret [sql run $ins]
        if {$ret <= 0} {
            # return error
            return -code error -errorcode $ret
        }

    } else {
        # fetch record
        set row [sql fetch $c ]
        Debug $row 9
        set ret [sql close $c]

        array set r $row
        set i_last  $r(i_last)
        set i_start  $r(i_start)
        set i_incr  $r(i_incr)
        set i_limit  $r(i_limit)
    }

    # if freshly inserted
    if  { $i_last == 0 } {
        set i_last $i_start
    }

    # check limit
    if { $i_limit > 0 && $i_last >= $i_limit } {
        set i_last $i_start
    }

    # now increment last and update row on the fly
    incr i_last $i_incr

    set upd "update $table set i_last = $i_last \
                    where i_idref = '$idref'"

    set ret [sql run $upd]
    if {$ret <= 0} {
        # return error
        return -code error -errorcode $ret
    }

    # commit if not within transaction !
    if {$DB_lib_priv(tr_stat) == 0 } {
        set ret [sql run "COMMIT WORK"]
    }

    y_db_replication write $i_last

    return $i_last
}