Functions in data structures

classic Classic list List threaded Threaded
14 messages Options
Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Joachim Durchholz
Hi all,

I've been toying with Erlang, preparing for a Real-World(TM) project,
and such a state provokes a specific kind of questions.

Mine are related to functions in data structures (I gather that
Erlangese for "data structure" is "term" - is this correct?)

Assume that a term contains a function. Further assume that this term is
converted to binary, transmitted to a different machine, and converted
back to an Erlang term; or assume it is stored in Mnesia and later read
back into Erlang. The base line is: the term is taken out of the Erlang
run-time system and later reconstructed.
The question is whether the function can be called after reconstructing
the term, under various circumstances:

1) Does it work if the function is a named function?
2) Does it work if the function is a named function, but the
    term is restored in an environment that doesn't contain
    that function?
    (E.g. the term is sent over the network and reconstructed
    in an Erlang run-time that doesn't have the same sources.)
3) Does it work if the function is anonymous?
4) Does it work if the function was constructed at run-time?
    (Most functional languages can construct functions at run-time
    by taking a given function and filling in a parameter with
    either a term or another function. I'm not sure that this is
    possible in Erlang, but I sincerely hope so...)

Regards,
Jo



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Ulf Wiger-4
On Mon, 16 Jun 2003, Joachim Durchholz wrote:

>Hi all,
>
>I've been toying with Erlang, preparing for a
>Real-World(TM) project, and such a state provokes a
>specific kind of questions.
>
>Mine are related to functions in data structures (I gather
>that Erlangese for "data structure" is "term" - is this
>correct?)
>
>Assume that a term contains a function. Further assume that
>this term is converted to binary, transmitted to a
>different machine, and converted back to an Erlang term; or
>assume it is stored in Mnesia and later read back into
>Erlang. The base line is: the term is taken out of the
>Erlang run-time system and later reconstructed. The
>question is whether the function can be called after
>reconstructing the term, under various circumstances:

The main issue when storing metadata persistently is how to
deal with upgrades. An issue when sending metadata to
another machine is whether that machine has the same code
loaded.

>1) Does it work if the function is a named function?

Yes, given that the named module is loaded on that machine.


>2) Does it work if the function is a named function, but the
>    term is restored in an environment that doesn't contain
>    that function?
>    (E.g. the term is sent over the network and reconstructed
>    in an Erlang run-time that doesn't have the same sources.)

No. Worst case (?) is that the named function exists, but
does something entirely different from what you expected.


>3) Does it work if the function is anonymous?

Anonymous functions are also tied to modules, so the same
answer as above applies, except anonymous functions may also
break if the module is replaced with an equivalent, but
heavily reorganized module. This will not break (1).

>4) Does it work if the function was constructed at run-time?
>    (Most functional languages can construct functions at run-time
>    by taking a given function and filling in a parameter with
>    either a term or another function. I'm not sure that this is
>    possible in Erlang, but I sincerely hope so...)

Anonymous functions are constructed at runtime (the logic is
static, but the closure is defined at runtime.) So the
answer is the same as for (3).

There is another option, seldomly exercized: you can send an
abstract form and evaluate it on the other side. This is
more stable in time and space than the other option, but
at the cost of slightly worse performance. The performance
issue can be partly overcome by on-the-fly compilation.

/Uffe
--
Ulf Wiger, Senior Specialist,
   / / /   Architecture & Design of Carrier-Class Software
  / / /    Strategic Product & System Management
 / / /     Ericsson AB, Connectivity and Control Nodes



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Francesco Mazzoli-2
In reply to this post by Joachim Durchholz
Hi Joachim,

you posed some interesting questions... If you are planing on a real
world application, I think the best approach is to test your questions
and ideas. Set up two erlang nodes, convert your fun to a binary and zip
it over. Or store it in mnesia. It will give you an immediate reply,
often faster than looking it up in the documentation. And most
certainly, more precise. That is what is so great and easy about Erlang.

Good luck in your project,
Francesco
--
http://www.erlang-consulting.com

Joachim Durchholz wrote:

> Hi all,
>
> I've been toying with Erlang, preparing for a Real-World(TM) project,
> and such a state provokes a specific kind of questions.
>
> Mine are related to functions in data structures (I gather that
> Erlangese for "data structure" is "term" - is this correct?)
>
> Assume that a term contains a function. Further assume that this term
> is converted to binary, transmitted to a different machine, and
> converted back to an Erlang term; or assume it is stored in Mnesia and
> later read back into Erlang. The base line is: the term is taken out
> of the Erlang run-time system and later reconstructed.
> The question is whether the function can be called after
> reconstructing the term, under various circumstances:
>
> 1) Does it work if the function is a named function?
> 2) Does it work if the function is a named function, but the
>    term is restored in an environment that doesn't contain
>    that function?
>    (E.g. the term is sent over the network and reconstructed
>    in an Erlang run-time that doesn't have the same sources.)
> 3) Does it work if the function is anonymous?
> 4) Does it work if the function was constructed at run-time?
>    (Most functional languages can construct functions at run-time
>    by taking a given function and filling in a parameter with
>    either a term or another function. I'm not sure that this is
>    possible in Erlang, but I sincerely hope so...)
>
> Regards,
> Jo
>
>
>




Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Fredrik Linder-2
In reply to this post by Ulf Wiger-4
-- ZIP --
> There is another option, seldomly exercized: you can send an
> abstract form and evaluate it on the other side. This is
> more stable in time and space than the other option, but
> at the cost of slightly worse performance. The performance
> issue can be partly overcome by on-the-fly compilation.
>
> /Uffe

Interesting, is that implemented within Erlang? And if so, where can I read
about it/how to use it?

/Fredrik



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Luke Gorrie-3
"Fredrik Linder" <fredrik.linder> writes:

> -- ZIP --
> > There is another option, seldomly exercized: you can send an
> > abstract form and evaluate it on the other side. This is
> > more stable in time and space than the other option, but
> > at the cost of slightly worse performance. The performance
> > issue can be partly overcome by on-the-fly compilation.
> >
> > /Uffe
>
> Interesting, is that implemented within Erlang? And if so, where can I read
> about it/how to use it?

Consider this shell fragment:

  (x)1> Node = node().
  x
  (x)2> F = fun() -> {fun_created_on, Node} end.
  #Fun<erl_eval.19.280769>

The last return value, #Fun<erl_eval.19.280769>, is not a fun created
directly from the expression {fun_created_on, Node}. Instead, the fun
was created in erl_eval, and it captures the current variable bindings
and the syntax-tree of the code it's actually supposed to run
(i.e. {fun_created_on, Node}).

Here is the bit of erl_eval that creates the fun:

 1. expr({'fun',Line,{clauses,Cs}}, Bs, Lf) ->
 2.     %% This is a really ugly hack!
 3.     case length(element(3,hd(Cs))) of
 4.         0 -> {value,fun () -> eval_fun(Cs, [], Bs, Lf) end,Bs};

If you actually call the fun:

  (x)3> F().  
  {fun_created_on,x}

The code it executes is actually the eval_fun(...) from line (4)
above. This then *interprets* the {fun_created_on, Node} expression,
which was captured in the erl_eval fun along with the set of variable
bindings.

Very groovy!

The nice part is that, so long as two nodes have compatible copies of
erl_eval, they will always be able to run each others' code in this
way.

BTW, here is a code snippet out of Distel's backend which uses
erl_eval directly:

  eval_expression(S) ->
      case parse_expr(S) of
          {ok, Parse} ->
              try_evaluation(Parse);
          {error, {_, erl_parse, Err}} ->
              {error, Err}
      end.

  try_evaluation(Parse) ->
      case catch erl_eval:exprs(Parse, []) of
          {value, V, _} ->
              {ok, flatten(io_lib:format("~p", [V]))};
          {'EXIT', Reason} ->
              {error, Reason}
      end.

  parse_expr(S) ->
      {ok, Scan, _} = erl_scan:string(S),
      erl_parse:parse_exprs(Scan).

Using that, you can create "portable" funs in your programs, like
this:

  1> distel:eval_expression("fun() -> hello() end.").
  {ok,"#Fun<erl_eval.19.280769>"}

Although if you want the funs to include variable bindings, you'll
need to pass them as a key-value list to that call to erl_eval:exprs,
where I just use the empty list.

Cheers,
Luke



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Joachim Durchholz
In reply to this post by Francesco Mazzoli-2
Francesco Cesarini wrote:
> If you are planing on a real
> world application,

This is indeed the case.

 > I think the best approach is to test your questions
> and ideas. Set up two erlang nodes, convert your fun to a binary and zip
> it over. Or store it in mnesia. It will give you an immediate reply,
> often faster than looking it up in the documentation. And most
> certainly, more precise.

Er... right, but it won't help me if a future version of Erlang behaves
differently.
Since I'm a relative newbie, there's also the risk that Erlang is doing
things slightly differently from what I expect. In other words, I risk
misinterpreting the results of my experiments.
Finally,

In summary: experimental programming is fun, but it's better for
verifying documentation than for finding out how things work.

> Good luck in your project,

Thanks :-)

Regards,
Jo



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Fredrik Linder-2
In reply to this post by Luke Gorrie-3
Thank you!

This was very informative, and as you said, this IS groovy. :-)

/Fredrik

> -----Original Message-----
> From: owner-erlang-questions
> [mailto:owner-erlang-questions]On Behalf Of Luke Gorrie
> Sent: den 17 juni 2003 12:30
> To: Fredrik Linder
> Cc: Erlang Questions
> Subject: Re: Functions in data structures
>
>
> "Fredrik Linder" <fredrik.linder> writes:
>
> > -- ZIP --
> > > There is another option, seldomly exercized: you can send an
> > > abstract form and evaluate it on the other side. This is
> > > more stable in time and space than the other option, but
> > > at the cost of slightly worse performance. The performance
> > > issue can be partly overcome by on-the-fly compilation.
> > >
> > > /Uffe
> >
> > Interesting, is that implemented within Erlang? And if so,
> where can I read
> > about it/how to use it?
>
> Consider this shell fragment:
>
>   (x)1> Node = node().
>   x
>   (x)2> F = fun() -> {fun_created_on, Node} end.
>   #Fun<erl_eval.19.280769>
>
> The last return value, #Fun<erl_eval.19.280769>, is not a fun created
> directly from the expression {fun_created_on, Node}. Instead, the fun
> was created in erl_eval, and it captures the current variable bindings
> and the syntax-tree of the code it's actually supposed to run
> (i.e. {fun_created_on, Node}).
>
> Here is the bit of erl_eval that creates the fun:
>
>  1. expr({'fun',Line,{clauses,Cs}}, Bs, Lf) ->
>  2.     %% This is a really ugly hack!
>  3.     case length(element(3,hd(Cs))) of
>  4.         0 -> {value,fun () -> eval_fun(Cs, [], Bs, Lf) end,Bs};
>
> If you actually call the fun:
>
>   (x)3> F().  
>   {fun_created_on,x}
>
> The code it executes is actually the eval_fun(...) from line (4)
> above. This then *interprets* the {fun_created_on, Node} expression,
> which was captured in the erl_eval fun along with the set of variable
> bindings.
>
> Very groovy!
>
> The nice part is that, so long as two nodes have compatible copies of
> erl_eval, they will always be able to run each others' code in this
> way.
>
> BTW, here is a code snippet out of Distel's backend which uses
> erl_eval directly:
>
>   eval_expression(S) ->
>       case parse_expr(S) of
>           {ok, Parse} ->
>               try_evaluation(Parse);
>           {error, {_, erl_parse, Err}} ->
>               {error, Err}
>       end.
>
>   try_evaluation(Parse) ->
>       case catch erl_eval:exprs(Parse, []) of
>           {value, V, _} ->
>               {ok, flatten(io_lib:format("~p", [V]))};
>           {'EXIT', Reason} ->
>               {error, Reason}
>       end.
>
>   parse_expr(S) ->
>       {ok, Scan, _} = erl_scan:string(S),
>       erl_parse:parse_exprs(Scan).
>
> Using that, you can create "portable" funs in your programs, like
> this:
>
>   1> distel:eval_expression("fun() -> hello() end.").
>   {ok,"#Fun<erl_eval.19.280769>"}
>
> Although if you want the funs to include variable bindings, you'll
> need to pass them as a key-value list to that call to erl_eval:exprs,
> where I just use the empty list.
>
> Cheers,
> Luke
>
>


Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Lennart Ohman
In reply to this post by Joachim Durchholz
> ...
> Er... right, but it won't help me if a future version of Erlang behaves
> differently.
 > ...

That is one thing which is good with Erlang. It *is* being used for
real-life systems. Meaning that the user community is rather restrictive
to changes that breaks old code, unless such bring substantial advantages
to most developers.

/Lennart


-------------------------------------------------------------
Lennart Ohman                   phone   : +46-8-587 623 27
Sjoland & Thyselius Telecom AB  cellular: +46-70-552 6735
Sehlstedtsgatan 6               fax     : +46-8-667 8230
SE-115 28 STOCKHOLM, SWEDEN     email   : lennart.ohman



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Joachim Durchholz
Lennart ?hman wrote:
>> ...
>> Er... right, but it won't help me if a future version of Erlang
>> behaves differently.
>
> That is one thing which is good with Erlang. It *is* being used for
> real-life systems. Meaning that the user community is rather restrictive
> to changes that breaks old code, unless such bring substantial advantages
> to most developers.

Oh, right.
Though I'm still reluctant to depend on undefined behaviour.

Anyway, I have decided to send just plain data, no functions. Code will
be sent in the form of software upgrades, just the way that people
expect things to work. (Sending functions implies all sorts of security
hassles anyway.)

Regards,
Jo



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Ulf Wiger (AL/EAB)
From: "Joachim Durchholz" <joachim.durchholz>

 
> Anyway, I have decided to send just plain data, no functions. Code will
> be sent in the form of software upgrades, just the way that people
> expect things to work. (Sending functions implies all sorts of security
> hassles anyway.)

You may want to include a version indicator in the messages going
between nodes. There are currently a few things that can mess up
certain code upgrades:

- record structures are compile-time dependent
- mnesia can not have multiple simultaneous versions of the schema

Thus, if you send records in messages between nodes, and have a
fully distributed database, it is a bit more difficult to upgrade code
one node at a time (an otherwise attractive option).

The issue of mnesia can be overcome by not mixing in too much
into one upgrade, and taking care to build a stable data model.
That is, if your upgrade doesn't contain schema changes, you can
upgrade one node at a time.

The issue of messages between nodes is a bit trickier. I don't think
there are any good guidelines about how to do this. Distribution
transparency is very nice (_very_ nice), but it does make in-service
upgrade a bit more difficult. UBF may offer some relief... The general
recommendation is to exercise more care in communication that
goes across node boundaries -- treat it as a set of formal interfaces.

The main reason for this rambling is that you need to spend some
time rather early thinking about what your upgrade requirements are,
and how they affect your design choices.


Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Håkan Mattsson-6
On Wed, 18 Jun 2003, Wiger Ulf wrote:

Uffe> The main reason for this rambling is that you need to spend some
Uffe> time rather early thinking about what your upgrade requirements are,
Uffe> and how they affect your design choices.

I wrote some internal notes about things to think about when
performing changes in the Mnesia application. The notes are
from 1998 and a bit outdated, but I think that they still can
serve as a source for inspiration when you think about
different types of future changes that you need to prepare
your own application for.

/H?kan

---
H?kan Mattsson
Ericsson
High Availability Software, DBMS Internals
http://www.ericsson.com/cslab/~hakan/
-------------- next part --------------

Mnesia upgrade policy (PA3)
===========================

This paper describes the upgrade policy of the Mnesia application.
It is divided into the following chapters:

        o Architecture overview
        o Compatibility
        o Configuration management
        o Compatibility requirements on other applications
        o Upgrade scenarios
        o Remaining issues

Architecture overview
---------------------

Mnesia is a distributed DataBase Management System (DBMS), appropriate
for telecommunications applications and other Erlang applications
which require continuous operation and exhibit soft real-time
properties. Mnesia is entirely implemented in in Erlang.

Meta data about the persistent tables is stored in a public ets table,
called mnesia_gvar. This ets table is accessed concurrently by several
kinds of processes:

* Static - on each node Mnesia has about 10 static
  processes. Eg. monitor, controller, tm, locker, recover, dumper...

* Dynamic - there are several kinds of dynamic processes are created
  for various purposes. Eg. tab_copier, perform_dump, backup_master ...

* Client processes created by Mnesia users. These invokes the Mnesia
  API to perform operations such as table lookups, table updates,
  table reconfigurations...

All these kinds of processes communicates with each other both locally
(on the same node) and remotely.

Mnesia may either be configured to use local disc or be to totally
disc less. All disc resident data is located under one directory and
is hosted by disk_log and dets.

The persistent tables may be backed up to external media. By default
backups are hosted by disk_log.


Compatibility
-------------

Each release of Mnesia has a unique version identifier. If Mnesia is
delivered to somebody outside the development team, the version
identifier is always changed, even if only one file differs from the
previous release of Mnesia. This means that the exact version of each
file in Mnesia can be determined from the version identifier of the
Mnesia application.

Mnesia does NOT utilize the concept of OTP "patches". The smallest
deliverable is currently the entire application. In future releases we
will probably deliver binaries, source code and different kinds of
documentation as separate packages.

Changes of the version identifier follows a certain pattern, depending
of how compatible the new release is with the previous dito. The
version identifier consists of 3 integer parts: Major.Minor.Harmless
(or just Major.Minor when Harmless is 0).

If a harmless detail has been changed, the "Harmless" part is
incremented. The change must not imply any incompatibility problems
for the user. An application upgrade script (.appup) could be
delivered in order to utilize code change in a running system, if
required.

If the change is more than a harmless detail (or if it is a harmless
change but has implied a substantial rewrite of the code), the "Minor"
part is incremented. The change must not imply any incompatibility
problems for the user. An application upgrade script (.appup) for code
change in a running system, is not always possible to deliver if the
change is complex. A full restart of Mnesia (and all applications
using Mnesia) may be required, potentially on all nodes
simultaneously.

All other kinds of changes implies the "Major" part to be incremented.
The change may imply that users of Mnesia needs to rewrite their code,
restart from backup or other inconveniences. Application upgrade
script (.appup) for code change in a running system is probably too
complex to write.

In any case it is a good idea to always read the release notes.


Configuration management
------------------------

Each major release constitutes an own development branch. Bugfixes,
new functionality etc. are normally performed in the latest major
development branch only. The last release of Mnesia is always the best
and customers are encouraged to upgrade to it.

Application upgrade scripts (.appup) are only written if they are
explicitly requested by a customer. Preparing the code for a
smoothless code change in a running system is very demanding and
restrains productive development. The code that handles the upgrade is
extremely hard to test, but it must be 100% correct. If it is not 100%
correct it is totally useless since the error may easily escalate to a
node restart or an inconsistent database.

It may however not always be possible to enforce a customer to accept
the latest release of incompatibility reasons. If a customer already
has a running system and encounters a serious bug in Mnesia, we may be
enforced to fix the bug in an old release. Such a bugfix is performed
in a separate bugfix branch (dedicated for this particular bugfix).
All source files in the release is labled with the version identifier
(e.g. "mnesia_3.4.1"). An application upgrade script (.appup) is
probably needed.


Compatibility requirements on other applications
------------------------------------------------

Mnesia is entirely implemented in Erlang and depends heavily on the
Erts, Kernel and StdLib applications.

Changes of storage format in dets and disk_log may cause these files
to be automatically upgraded to the new format, but only if Mnesia
allows them to perform the "repair". As an effect of such a format
upgrade it is likely that the Mnesia files cannot be read by older
releases, but it may be acceptable that old releases not are forward
compatible.

Mnesia stores its data as binaries. Changes of the external binary
format must also be backward compatible. Automatic conversion to
the new format is required.

Note, that Mnesia backups may be archived in years and that this
requires Erts and Kernel to be backward compatible with very old
binary formats and disk_log formats.


Upgrade scenarios
-----------------

There are a wide range of upgrade scenarios that needs to be analyzed
before they occur in reality. Here follows a few of them:

Backup format change.

  In the abstract header of the backup there is a version tag that
  identifies the format of the rest of the backup. Backups may be
  archived for a long period of time and it is important that
  old backups can be loaded into newer versions of the system.
  The backup format is Mnesia's survival format.

  Changes in the abstract backup format requires the backup version
  tag to be incremented and code to be written to convert the old
  format at backup load time. Minor change.

  The concrete format is an open format handled by a callback module
  and it is up to the callback module implementors to handle future
  changes. The default callback module uses disk_log and Mnesia relies
  heavily on disk_log's ability to automatically convert old disk_log
  files into new dito if the concrete format has been changed.

Transaction log file format change.

  In the abstract header of the transaction log there is a version tag
  that identifies the format of the rest of the log. Changes in the
  abstract transaction log format requires the transaction log version
  tag to be incremented and code to be written to convert the old
  format when the log is dumped at startup. Minor change.

  The concrete format is hidden by disk_log and Mnesia relies on
  disk_log's ability to automatically convert old disk_log files into
  new dito if the concrete format has been changed.

  If the abstract format change is severe or if disk_log cannot
  handle old disk_log formats the entire Mnesia database has to
  be backed up to external media and then installed as fallback.
  It may in worst case be neccessary to implement a new backup
  callback module that does not make use of disk_log. Major change.

Decision table log file format change.

  In the abstract header of the decision table log file there is a
  version tag that identifies the format of the rest of the
  log. The concrete format is hidden by disk_log and severe changes
  in the abstract or concrete formats are handled in the same way
  as complex changes of the transaction log file format: back the
  database up and re-install it as a fallback. Major change.

  Minor changes in the abstract format can be handled by conversion
  code run at startup. Minor change.

.DAT file format change.

  .DAT files has no abstract version format identifier.

  The concrete format is hidden by dets and Mnesia relies on
  dets' ability to automatically convert old dets files into
  new dito if the concrete format has been changed.

  Changes in the abstract or incompatible changes in the concrete
  format are are handled in the same way as complex changes in the
  transaction log file format: back the database up and re-install
  it as a fallback.

Core file format change.

  The core file is a debugging aid and contains essential information
  about the database. The core is automatically produced when Mnesia
  encounters a fatal error or manually by the user for the purpose
  of enclosing it into a trouble report. The core file consists of
  list of tagged tuples stored as a large binary. Future changes of
  the core format can easily be handled by adding a version tag
  first in the list if neccessary.

Persistent schema format change.
 
  The schema is implemented as an ordinary table with table names
  as key and a list of table properties as single attribute.
  Each table property is represented as a tagged tuple and adding
  new properties will not break any code. Minor change.

  Incompatible changes of the schema representation can be handled
  in the same way as complex changes of the transaction log file
  format: back the database up and re-install it as a fallback.
  Major change.

  If the change is severe and impacts the backup format then it should
  not be performed at all.

Renaming schema table to mnesia_schema.

 All internal (d)ets tables are prefixed with 'mnesia_' in order
 to avoid name conflicts with other applications. The only
 exception is the table named 'schema'.

 Renaming 'schema' to 'mnesia_schema' is a major change, that
 may breaks much customer code if it is not done very careful,
 since the name of the table is a part of the Mnesia API.
 The least intrusive change would be to leave the name 'schema'
 as a part of the API, but internally use the name 'mnesia_schema'
 and map all usages of 'schema' in all internal modules that accesses
 the schema table. It is however questionable if the change is
 worth the work effort.

Transient schema format change

  The transient schema information is stored in a public ets table
  named mnesia_gvar. Changes in the transient schema format affects
  a lot of processes and it is not feasible to change it dynamically.
  A restart on all db_nodes is required. Minor change.


Configuration parameter change. *** partly not supported ***

  Many of the configuration parameters ought to be possible to
  be changed dynamically in a running system:

     access_module
     backup_module
     debug
     dump_log_load_regulation
     dump_log_time_threshold
     dump_log_update_in_place
     dump_log_write_threshold
     event_module
     extra_db_nodes

  The remaining configuration parameters are only interesting at
  startup and any dynamic change of these should silently be ignored:

     auto_repair
     dir
     embedded_mnemosyne
     ignore_fallback_at_startup
     max_wait_for_decision
     schema_location

Inter node protocol change. *** partly not supported ***

  When Mnesia on one node tries to connect to Mnesia on another
  node it negotiates with the other node about which inter node
  communication protocol to use. A list of all known protocols
  identifiers are sent to the other node which replies with the
  protocol that it prefers. The other node may also reject the
  connection. Always when the inter node protocol needs to be changed,
  the protocol identifier is changed.

  If the change is a compatible change and we need to upgrade the
  protocol in a running system things gets a little bit complicated.
  A new version of the software which understands both the old an new
  protocols must be loaded on all nodes as a first step. All processes
  that uses old code must somehow switch to use the new code. One
  severe problem here is to locate all processes that needs a code
  switch. Server processes are fairly straight forward to manage.
  Client processes that are waiting in a receive statement are far
  more difficult to first locate and then force a code switch.
  We may prepare for the code switch by letting the client be
  able to receive a special code switch message and then continue
  to wait for interesting messages. (gen_server does not handle this
  but since Mnesia does not use gen_server's for performance critical
  processes or crash sensitive processes Mnesia can handle this in
  many cases.)

  If the new protocol is a pure extension of the old dito we may
  go ahead and use the new protocol and bump up the protocol
  version identifier.

  More complex protocol changes are sometimes possible to handle,
  but since they are extreamly hard to test it is probably better
  to restart the system.
   
  If the change is an incompatible change, Mnesia must be restarted on
  all nodes.


Intra node protocol change.

  Changing protocol between processes on the same node is slightly
  easier than changing inter node protocols. The difference is that
  we may iterate over all nodes and restart them one and one until
  the software has started to use the new protocols on all nodes.

Adding a new static process.

  Adding a new static process in Mnesia is a minor change,
  but cannot be handled by the supervisor "application"
  if the shutdown order of the static processes are important.
  The most relieable approach here is to restart Mnesia.

Adopt to new functionality in Erts, Kernel or StdLib:

  When was the new functionality introduced?
  Can Mnesia use the new functionality and still
  run on older Erlang/OTP releases?

Changing a function in the Mnesia API.
       
  Changes of module name, function name, arguments or
  semantics of a function is likely to be a major change.

  Adding a brand new function is a minor change.

Changing a Mnesia internal function.
       
  If it is possible to locate all processes that may
  invoke the function it may be worth to handle the
  code change in a running system. The safe approach
  is to restart the node. The processes must be able
  to handle 'sys' messages and export 'sys' callback
  functions. Minor change.

Process state format change.

  If it is possible to locate all processes that may
  invoke the function it may be worth to handle the
  code change in a running system. The safe approach
  is to restart the node. The processes must be able
  to handle 'sys' messages and export 'sys' callback
  functions. Minor change.
 

Code change to fix a harmless bug.

  Does these exist in reality or only in the Erlang book?

Code change to fix an inconsistency bug.

  A bug like all others but the effect of it is an
  inconsistent database. Mnesia cannot repair the database
  and it is up to the Mnesia users to make it consistent
  again. There are three options here:

  - ignore the fact that the database is inconsistent
  - back up the database and make the backup consistent
    before installing it as a fallback.
  - fix the inconsistency on-line while all applications
    are accessing the database

Code change to prevent an indefinite wait (deadlock, protocol mismatch).

  A bug like all others but the effect of it is hanging
  processes. Restart Mnesia on one or more nodes.

Remaining issues
----------------

The following issues remains to be implemented:

        - Dynamic configuration parameter change
        - Prepare code switch of client code

Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Joachim Durchholz
Hakan Mattsson wrote:
>
> I wrote some internal notes about things to think about when
> performing changes in the Mnesia application. The notes are
> from 1998 and a bit outdated, but I think that they still can
> serve as a source for inspiration when you think about
> different types of future changes that you need to prepare
> your own application for.

Thanks, these notes have been most useful.
Many (most) of them seem to pertain to the development of Mnesia resp.
Erlang itself, is this correct?

Regards,
Jo



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Joachim Durchholz
In reply to this post by Ulf Wiger (AL/EAB)
Wiger Ulf wrote:

> From: "Joachim Durchholz" <joachim.durchholz>
>>Anyway, I have decided to send just plain data, no functions. Code will
>>be sent in the form of software upgrades, just the way that people
>>expect things to work. (Sending functions implies all sorts of security
>>hassles anyway.)
>
> You may want to include a version indicator in the messages going
> between nodes. There are currently a few things that can mess up
> certain code upgrades:
>
> - record structures are compile-time dependent
> - mnesia can not have multiple simultaneous versions of the schema
>
> Thus, if you send records in messages between nodes, and have a
> fully distributed database, it is a bit more difficult to upgrade code
> one node at a time (an otherwise attractive option).

Fortunately, it's unlikely that full records will ever be sent directly.

The general structure will be as follows:

One server, distributed over several physical machines. Distribution is
both for scalability and redundancy; there is no communication between
server machines except Mnesia replication and some load-balancing
protocol. The server is intended to run 24/7.
Lots of clients, each sitting on one physical machine. Clients interact
just with the server, not directly with each other. Clients will run for
a short period and terminate.

In this scenario, changes in the protocol between client and server are
most likely to cause problems.
Changes in the Mnesia schema on the server comes next. I've been
thinking about adding a version number to each table name, and add a
layer that retrieves and converts data from previous-version tables if
the current-version table doesn't have the data. Updates simply go to
the latest version of the table, resulting in a convert-on-the-fly
schema. (The critical point here is conversion, which must be 100%
correct - but that would have to be correct whether the conversion is
done in batch mode or on the fly.)

> The main reason for this rambling is that you need to spend some
> time rather early thinking about what your upgrade requirements are,
> and how they affect your design choices.

I hope the above doesn't contain too many thinkos :-)

Regards,
Jo



Reply | Threaded
Open this post in threaded view
|

Functions in data structures

Ulf Wiger-4
On Wed, 18 Jun 2003, Joachim Durchholz wrote:

>Changes in the Mnesia schema on the server comes next. I've
>been thinking about adding a version number to each table
>name, and add a layer that retrieves and converts data from
>previous-version tables if the current-version table
>doesn't have the data. Updates simply go to the latest
>version of the table, resulting in a convert-on-the-fly
>schema. (The critical point here is conversion, which must
>be 100% correct - but that would have to be correct whether
>the conversion is done in batch mode or on the fly.)

There is a transform_table() function in mnesia, which can
be used for making in-service schema updates and converting
all affected data within a transaction. This requires the
users of the data to be suspended during the transformation,
and that the users jump into new versions of the code before
they resume.

One suggestion is to begin using mnesia through an access
module and not using the mnesia functions directly. In fact,
the only function you really need to stub is the
mnesia:activity() function. If you consistently use your own
wrapper function

myMod:activity(Type, F) ->
  mnesia:activity(Type, F).

You can later change this function to

myMod:activity(Type, F) ->
  mnesia:activity(Type, F, [], myAccessModule)

where you can do all sorts of esoteric stuff in
myAccessModule (the rdbms contribution uses this method)

*** Example:
In the event of really tricky upgrades, you can use some
clever tricks in this access module, basically hiding the
nastiness from the applications.

I've attached what should be regarded as a prototype attempt
at handling redundancy reboot upgrades including schema
changes to mnesia. The idea is that one begins the upgrade
by loading an "upgrade module" on all nodes, containing no
other functionality than such that prepares for the upgrade.
The included mnesia access module provides a way to upgrade
a table definition using a forward transform for already
upgraded nodes, and a backward transform for not yet
upgraded nodes.

I've tested it for simple cases. It's never been used in a
real live upgrade, but it perhaps illustrates the potential.

/Uffe
--
Ulf Wiger, Senior Specialist,
   / / /   Architecture & Design of Carrier-Class Software
  / / /    Strategic Product & System Management
 / / /     Ericsson AB, Connectivity and Control Nodes

-------------- next part --------------
%%% The contents of this file are subject to the Erlang Public License,
%%% Version 1.0, (the "License"); you may not use this file except in
%%% compliance with the License. You may obtain a copy of the License at
%%% http://www.erlang.org/license/EPL1_0.txt
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
%%% The Original Code is sysDb-0.1
%%%
%%% The Initial Developer of the Original Code is Ericsson Telecom
%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%%% Telecom AB. All Rights Reserved.
%%%
%%% Contributor(s):
%%%
%%% #0.   BASIC INFORMATION
%%% ----------------------------------------------------------
%%% %CCaseFile: sysDb.erl %
%%% Author:          Ulf Wiger <ulf.wiger>
%%% Description:     mnesia rwapper for enhanced upgrade support
%%%
%%% Modules used:    mnesia
%%%
%%% ----------------------------------------------------------
-module(sysDb).
-id('94/190 55-CNA 121 70 Ux').
-vsn('/main/R7A/10').
-date('01-04-11').

-ifdef(debug).
-define(dbg(Fmt, Args), io:format("~p-~p: " ++ Fmt, [?MODULE,?LINE|Args])).
-else.
-define(dbg(Fmt,Args), no_debug).
-endif.

%%% #2.    EXPORT LISTS
%%% ----------------------------------------------------------
%%% #2.1   EXPORTED INTERFACE FUNCTIONS
%%% ----------------------------------------------------------

%% Substitute for mnesia:transaction/1.
-export([transaction/1,
         activity/2]).

-export([begin_upgrade/0,
         activate_new_tables/0,
         end_upgrade/0]).

-export([table_transform/4,
         table_transform/5]).

-export([init_tables/0]).

%% Update
-export([lock/4,
         write/5,
         delete/5,
         delete_object/5,
         read/5,
         match_object/5,
         all_keys/4,
         index_match_object/6,
         index_read/6,
         table_info/4]).



%%% ----------------------------------------------------------
%%% #2.2   EXPORTED INTERNAL FUNCTIONS
%%% ----------------------------------------------------------

-define(TEMP, sysDbTemp).
-define(TEMP_BAG, sysDbTempBag).
-define(LOCAL, sysDb_local).
-define(GLOBALS, sysDbGlobals).

-record(?TEMP, {key, value}).
-record(?TEMP_BAG, {key, value}).
-record(?LOCAL, {key, value}).
-record(?GLOBALS, {key, value}).



%%% #3.    CODE
%%% #---------------------------------------------------------
%%% #3.1   CODE FOR EXPORTED INTERFACE FUNCTIONS
%%% #---------------------------------------------------------


%%%----------------------------------------------------------------------
%%% -type activity(Fun : function())->
%%%     ResultFromFun.
%%% Input: Function object for Mnesia transaction
%%% Output: Result from
%%% Exceptions: EXIT if transaction aborted
%%% Description: This is a wrapper around mnesia:activity/1.
%%%    It starts a mnesia activity with this module as the activity
%%%    module. This enables all SYSDB integrity checks.
%%%----------------------------------------------------------------------


transaction(Fun) ->
    case is_upgrade() of
        false ->
            mnesia:transaction(Fun);
        true ->
            case catch mnesia:activity(transaction, Fun, [], ?MODULE) of
                {'EXIT', Reason} ->
                    {aborted, Reason};
                Result ->
                    {atomic, Result}
            end
    end.

activity(Type, Fun) ->
    case is_upgrade() of
        false ->
            mnesia:activity(Type, Fun);
        true ->
            mnesia:activity(Type, Fun, [], ?MODULE)
    end.

begin_upgrade() ->
    mnesia:activity(sync_dirty,
                    fun() ->
                            mnesia:write(#?GLOBALS{key = is_upgrade,
                                                   value = true})
                    end).

table_transform(Tab, FwdFun, BwdFun, NewAttributeList) ->
    RecName = mnesia:table_info(Tab, record_name),
    table_transform(Tab, FwdFun, BwdFun, NewAttributeList, RecName).

table_transform(Tab, FwdFun, BwdFun, NewAttributeList, NewRecordName) ->
    Key = {conversion, Tab},
    Val = {FwdFun, BwdFun, NewAttributeList, NewRecordName},
    mnesia:activity(sync_dirty,
                    fun() ->
                            mnesia:write(#?GLOBALS{key = Key,
                                                   value = Val})
                    end).


activate_new_tables() ->
    Tabs = ets:match_object(?GLOBALS, #?GLOBALS{key = {conversion, '_'},
                                                value = '_'}),
    lists:foreach(
      fun(#?GLOBALS{key = {conversion, Tab}}) ->
              mnesia:dirty_write(#?LOCAL{key = {version, Tab},
                                         value = new})
      end, Tabs).

end_upgrade() ->
    %% We split the conversion/copy operations into multiple transactions
    %% In order to make sure there are no updates in the meantime.
    %% Currently, no action is taken to ensure that already started
    %% activities actually finish before we commit the table transforms.
    %% however, chances are high that ets, dirty, and sync_dirty operations
    %% are lightweight enough that they will finish well before the transforms
    %% below are activated. Regular transactions are trickier, since they
    %% *might* end up waiting behind on of our table locks, and then continue
    %% believing that an upgrade is still in progress. We should deal with
    %% this in lock() below... exactly how, I'm not sure.
    mnesia:activity(sync_dirty,
                    fun() ->
                            mnesia:write(#?GLOBALS{key = is_upgrade,
                                                   value = wait})
                    end),
    Tabs = ets:match_object(?GLOBALS, #?GLOBALS{key = {conversion, '_'},
                                                value = '_'}),

    lists:foreach(
      fun(#?GLOBALS{key = {conversion, Tab},
                    value = {FwF, BwF, NewAttrs, NewRecName}}) ->
              {atomic, ok} =
                  mnesia:transform_table(Tab, FwF,
                                         NewAttrs, NewRecName)
      end, Tabs),
    ok=io:format("transforms complete~n", []),
    lists:foreach(
      fun(#?GLOBALS{key = {conversion, Tab},
                    value = {FwF, BwF, NewAttrs, NewRecName}}) ->
              F = fun() ->
                          mnesia:delete({?GLOBALS, {conversion, Tab}}),
                          copy_new_objs(Tab)
                  end,
              mnesia:activity(transaction, F)
      end, Tabs),
    ok=io:format("copy complete~n", []),
    mnesia:activity(sync_dirty,
                    fun() ->
                            mnesia:delete({?GLOBALS, is_upgrade})
                    end).


init_tables() ->
    Nodes = mnesia:system_info(running_db_nodes),
    create_table(?LOCAL, [{ram_copies, Nodes},
                          {type, set},
                          {local_content, true},
                          {attributes, record_info(fields, ?LOCAL)}]),
    create_table(?TEMP, [{ram_copies, Nodes},
                         {type, set},
                         {attributes, record_info(fields, ?TEMP)}]),
    create_table(?TEMP_BAG, [{ram_copies, Nodes},
                             {type, bag},
                             {attributes, record_info(fields, ?TEMP_BAG)}]),
    create_table(?GLOBALS, [{ram_copies, Nodes},
                            {type, set},
                            {attributes, record_info(fields, ?GLOBALS)}]).

%% mnesia callbacks =====================================
%%   for each callback, an internal function is implemented.
%%   the internal functions are not exported, since they are only
%%   meant to be used for operations generated by the dictionary.
%%   No integrity checks are performed on the internal functions.


lock(ActivityId, Opaque, LockItem, LockKind) ->
    mnesia:lock(ActivityId, Opaque, LockItem, LockKind).


write(ActivityId, Opaque, Tab, Rec, LockKind) ->
    case table_conversion_type(Tab) of
        none ->
            mnesia:write(ActivityId, Opaque, Tab, Rec, LockKind);
        {Version, FwdF, BwdF} ->
            {OldRec, NewRec} =
                case Version of
                    old ->
                        {Rec, FwdF(Rec)};
                    new ->
                        {BwdF(Rec), Rec}
                end,
            TempKey = {_, RecKey} = temp_key(Tab, Rec),
            case mnesia:table_info(Tab, type) of
                bag ->
                    %% in order to implement some of the other access
                    %% functions relatively safely, we must move all
                    %% objects with the same key to ?TEMP_BAG
                    case mnesia:read(ActivityId, Opaque, ?TEMP_BAG,
                                     RecKey, LockKind) of
                        [] ->
                            OldObjs = mnesia:read(ActivityId, Opaque,
                                                  Tab, RecKey, LockKind),
                            lists:foreach(
                              fun(X) ->
                                      TempKeyX = temp_key(Tab, X),
                                      mnesia:write(ActivityId, Opaque,
                                                   ?TEMP_BAG,
                                                   {TempKeyX, X}, LockKind)
                              end, OldObjs);
                        _ ->
                            %% This has been done previously
                            ok
                    end,
                    mnesia:write(ActivityId, Opaque, ?TEMP_BAG,
                                 {TempKey, NewRec}, LockKind);
                _ ->
                    mnesia:write(ActivityId, Opaque, ?TEMP,
                                 #?TEMP{key = TempKey,
                                        value = NewRec},
                                 LockKind)
            end,
            mnesia:write(ActivityId, Opaque, Tab, OldRec, LockKind)
    end.


delete(ActivityId, Opaque, Tab, Key, LockKind) ->
    case table_conversion_type(Tab) of
        none ->
            mnesia:delete(ActivityId, Opaque, Tab, Key, LockKind);
        _ ->
            case mnesia:table_info(Tab, type) of
                bag ->
                    mnesia:delete(ActivityId, Opaque, ?TEMP_BAG,
                                  {Tab, Key}, LockKind);
                _ ->
                    %% set or ordered_set
                    mnesia:delete(ActivityId, Opaque, ?TEMP,
                                  {Tab, Key}, LockKind)
            end,
            mnesia:delete(ActivityId, Opaque, Tab, Key, LockKind)
    end.

delete_object(ActivityId, Opaque, Tab, Rec, LockKind) ->
    %% WARNING!!!
    %% We cannot be sure that a transform hits the target, since
    %% attributes which have been added/removed are most likely initialized
    %% with default values in one direction or the other. Thus, an object
    %% written during the upgrade may not be delete:able with this function.
    case table_conversion_type(Tab) of
        none ->
            mnesia:delete_object(ActivityId, Opaque, Tab, Rec, LockKind);
        {new, FwF, BwF} ->
            NewRec = FwF(Rec),
            Key = temp_key(Tab, Rec),
            mnesia:delete_object(ActivityId, Opaque, Tab, Rec, LockKind),
            TempTab = case mnesia:table_info(Tab, type) of
                          bag -> ?TEMP_BAG;
                          _ ->   ?TEMP
                      end,
            mnesia:delete_object(ActivityId, Opaque, TempTab,
                                 {Key, NewRec}, LockKind);
        {old, FwF, BwF} ->
            OldRec = BwF(Rec),
            Key = temp_key(Tab, Rec),
            mnesia:delete_object(ActivityId, Opaque, Tab, OldRec, LockKind),
            TempTab = case mnesia:table_info(Tab, type) of
                          bag -> ?TEMP_BAG;
                          _ ->   ?TEMP
                      end,
            mnesia:delete_object(ActivityId, Opaque, TempTab,
                                 {Key, Rec}, LockKind)
    end.


read(ActivityId, Opaque, Tab, Key, LockKind) ->
    case table_conversion_type(Tab) of
        none ->
            mnesia:read(ActivityId, Opaque, Tab, Key, LockKind);
        {Version, FwF, BwF} ->
            case mnesia:table_info(Tab, type) of
                bag ->
                    case mnesia:read(ActivityId, Opaque, ?TEMP_BAG,
                                     {Tab, Key}, LockKind) of
                        [] ->
                            mnesia:read(ActivityId, Opaque, Tab,
                                        Key, LockKind);
                        Objs ->
                            %% see the implementation of write() -- we know
                            %% that the ?TEMP_BAG table holds all objects
                            %% with Key.
                            case Version of
                                new ->
                                    [O || #?TEMP_BAG{value = O} <- Objs];
                                old ->
                                    [BwF(O) || #?TEMP_BAG{value = O} <- Objs]
                            end
                    end;
                _ ->
                    case mnesia:read(ActivityId, Opaque, ?TEMP,
                                     {Tab, Key}, LockKind) of
                        [] ->
                            case {mnesia:read(ActivityId, Opaque, Tab,
                                              Key, LockKind), Version} of
                                {[], _} ->
                                    [];
                                {[Obj], new} ->
                                    [FwF(Obj)];
                                {[Obj], old} ->
                                    [Obj]
                            end;
                        [#?TEMP{value = Obj}] ->
                            case Version of
                                new ->
                                    [Obj];
                                old ->
                                    [BwF(Obj)]
                            end
                    end
            end
    end.



match_object(ActivityId, Opaque, Tab, Pattern, LockKind) ->
    case table_conversion_type(Tab) of
        none ->
            mnesia:match_object(ActivityId, Opaque, Tab, Pattern, LockKind);
        {old, _, _} ->
            mnesia:match_object(ActivityId, Opaque, Tab, Pattern, LockKind);
        {new, FwF, BwF} ->
            match_object1(ActivityId, Opaque, Tab, Pattern, LockKind)
    end.

match_object1(ActivityId, Opaque, Tab, Pattern, LockKind) ->
    case is_var(Pattern) of
        true ->
            %% must search whole table
            search_whole_tab(ActivityId, Opaque, Tab, Pattern, LockKind);
        false ->
            KeyPos = mnesia:table_info(Tab, keypos),
            if size(Pattern) >= KeyPos ->
                    Key = element(KeyPos, Pattern),
                    case is_ground(Key) of
                        true ->
                            Objs = read(ActivityId, Opaque,
                                        Tab, Key, LockKind),
                            match_objs(Objs, Pattern);
                        false ->
                            %% must search whole table
                            search_whole_tab(ActivityId, Opaque,
                                             Tab, Pattern, LockKind)
                    end;
               true ->
                    []
            end
    end.


match_objs([], _) ->
    [];
match_objs(Objs, Pattern) ->
    Tab = ets:new(match_tab, [set]),
    MatchPat = {x, Pattern},
    match_objs(Objs, Tab, MatchPat).

match_objs([], Tab, Pattern) ->
    [];
match_objs([O|Objs], Tab, Pattern) ->
    %% It's not trivial to write an Erlang version of the ETS pattern match,
    %% so I cheat and do this instead.
    ets:insert(Tab, {x, O}),
    case ets:match_object(Tab, Pattern) of
        [] ->
            match_objs(Objs, Tab, Pattern);
        [_] ->
            [O|match_objs(Objs, Tab, Pattern)]
    end.

search_whole_tab(ActivityId, Opaque, Tab, Pattern, LockKind) ->
    ETS = ets:new(match_tab, [set]),
    MatchPat = {x, Pattern},
    Keys = mnesia:all_keys(ActivityId, Opaque, Tab,
                           ETS, Pattern, MatchPat, LockKind),
    search_whole_tab(Keys, ActivityId, Opaque, Tab,
                     ETS, Pattern, MatchPat, LockKind).

search_whole_tab([K|Keys], ActivityId, Opaque, Tab,
                 ETS, Pattern, MatchPat, LockKind) ->
    Objs = read(ActivityId, Opaque, Tab, K, LockKind),
    MatchedObjs = match_objs(Objs, ETS, MatchPat),
    MatchedObjs ++ search_whole_tab(Keys, ActivityId, Opaque, Tab,
                                    ETS, Pattern, MatchPat, LockKind);
search_whole_tab([], ActivityId, Opaque, Tab,
                 ETS, Pattern, MatchPat, LockKind) ->
    [].

                     
%% is_var(Atom) -> bool().

is_var(P) when atom(P) ->
    case atom_to_list(P) of
        [$_] -> true;
        [$$|Cs] -> digits(Cs);
        Other -> false
    end;
is_var(P) -> false.

digits([C|Cs]) when integer(C), C >= $0, C =< $9 -> digits(Cs);
digits([C|Cs]) -> false;
digits([]) -> true.
%% is_ground(Term) -> bool().


is_ground([H|T]) ->
    case is_ground(H) of
        true -> is_ground(T);
        false -> false
    end;
is_ground([]) -> true;
is_ground(P) when tuple(P) ->
    is_ground_tuple(P, 1, size(P));
is_ground(P) -> not is_var(P).

is_ground_tuple(P, I, Size) when I > Size -> true;
is_ground_tuple(P, I, Size) ->
    case is_ground(element(I, P)) of
        true -> is_ground_tuple(P, I+1, Size);
        false -> false
    end.


%%% ================== The following callback functions have not been
%%% ================== dealt with yet.

all_keys(ActivityId, Opaque, Tab, LockKind) ->
    mnesia:all_keys(ActivityId, Opaque, Tab, LockKind).


index_match_object(ActivityId, Opaque, Tab, Pattern, Attr, LockKind) ->
    mnesia:index_match_object(ActivityId, Opaque, Tab,
                              Pattern, Attr, LockKind).


index_read(ActivityId, Opaque, Tab, SecondaryKey, Attr, LockKind) ->
    mnesia:index_read(ActivityId, Opaque, Tab, SecondaryKey, Attr, LockKind).


table_info(ActivityId, Opaque, Tab, InfoItem) ->
    mnesia:table_info(ActivityId, Opaque, Tab, InfoItem).




%%% #---------------------------------------------------------
%%% #3.2   CODE FOR EXPORTED INTERNAL FUNCTIONS
%%% #---------------------------------------------------------


%%% #---------------------------------------------------------
%%% #3.3   CODE FOR INTERNAL FUNCTIONS
%%% #---------------------------------------------------------

temp_key(Tab, Obj) ->
    {Tab, key_of(Tab, Obj)}.

key_of(Tab, Obj) ->
    Pos = mnesia:table_info(Tab, keypos),
    element(Pos, Obj).

table_conversion_type(Tab) ->
    case ets:lookup(?GLOBALS, {conversion, Tab}) of
        [] ->
            none;
        [#?GLOBALS{value = {FwF, BwF, NewAttrs, NewRecName}}] ->
            Version = case ets:lookup(?LOCAL, {version, Tab}) of
                          [] ->
                              %% we're still using the old
                              old;
                          [#?LOCAL{value = new}] ->
                              new
                      end,
            {Version, FwF, BwF}
    end.

copy_new_objs(Tab) ->
    case mnesia:table_info(Tab, type) of
        bag ->
            Pat = #?TEMP_BAG{key = {Tab, '_'}, value = '_'},
            NewObjs = ets:match_object(?TEMP_BAG, Pat),
            Buf = ets:new(buf, [set]),
            KeyPos = mnesia:table_info(Tab, keypos),
            copy_bag_objs(NewObjs, Tab, KeyPos, Buf),
            ets:delete(Buf);
        _ ->
            %% ordered_set or set
            Pat = #?TEMP{key = {Tab, '_'}, value = '_'},
            NewObjs = ets:match_object(?TEMP, Pat),
            lists:foreach(
              fun(#?TEMP{key = TempKey, value = Obj}) ->
                      mnesia:delete({?TEMP, TempKey}),
                      mnesia:write(Obj)
              end, NewObjs)
    end.

copy_bag_objs([#?TEMP_BAG{key=TempKey, value=Obj}|Objs], Tab, KeyPos, Buf) ->
    Key = element(KeyPos, Obj),
    case ets:lookup(Buf, Key) of
        [] ->
            ets:insert(Buf, {Key, 1}),
            mnesia:delete({Tab, Key}),
            mnesia:delete({?TEMP_BAG, TempKey}),
            mnesia:write(Obj);
        _ ->
            mnesia:write(Obj)
    end,
    copy_bag_objs(Objs, Tab, KeyPos, Buf);
copy_bag_objs([], Tab, KeyPos, Buf) ->
    ok.

create_table(Tab, Opts) ->
    case mnesia:create_table(Tab, Opts) of
        {atomic, ok} ->
            ok;
        Other ->
            exit(Other)
    end.

is_upgrade() ->
    case ets:lookup(?GLOBALS, is_upgrade) of
        [] ->
            false;
        [#?GLOBALS{value = wait}] ->
            receive
            after 100 ->
                    is_upgrade()
            end;
        [#?GLOBALS{value = true}] ->
            true
    end.

%%%----------------------------------------------------------------------
%%% #3.3.1   Code for Additional Actions (post-transaction triggers).
%%%----------------------------------------------------------------------

%%% #4     CODE FOR TEMPORARY CORRECTIONS
%%% #---------------------------------------------------------