+1-757-461-3022 x124

Packages for xTuple ERP extensions

The xTuple Updater was originally designed to upgrade databases from one edition of xTuple ERP to another. It has been changed to also support loading extension packages which enhance and expand what xTuple ERP can do.

This page expands on the content of Creating Updater Packages, focusing on creating extensions. It starts with some general guidelines and finishes with a description of package syntax.

General Guidelines

Here are some general rules you should follow if you are creating an add-on package for xTuple ERP:

  • Name your package. Please give your package a name using the name attribute of the <package> element. This allows the updater to isolate your contributions from the core of xTuple ERP. You can add reports, scripts, user interface definitions, custom commands, etc. as part of your package and they will be found by xTuple ERP without any problems. Help yourself maintain your package, help the rest of us maintain xTuple ERP, and help the community of joint users of xTuple ERP and your package by keeping your contributions bundled together when deployed as well as for distribution.

  • Give your package a version number. Even in the early stages of package development, use the version attribute of the <package> element. This will help you and your users keep track of issues and compatibility problems.

  • Qualify references to database objects with a schema prefix. If you create a new phones view in your package named phonedirectory and a script that queries this view, the script should write the query to SELECT ... FROM phonedirectory.phones .... This ensures that your query always get data from the phones view in your package and not the phones table or view that someone else might create in another package or that xTuple might eventually add to the core product.

  • Keep your package as small as possible but still provide the functionality you set out to provide. You can always use package dependencies to manage simple relationships between packages instead of lumping a bunch of different features together simply because they share a common core. It would be better to have the core as its own package and each set of related features in their own packages, each marked as dependent on that core.
  • Use the developer attribute to name yourself or your company as the creator of the extension. You should also provide some way for people to contact you. You could use your company's web site URL for the developer attribute or put an email address and phone number in the package notes. This lets users find you to request support or features, report bugs, or pay you for your effort if that's your goal.

  • Use the <script> and related tags sparingly if at all. You could create tables or privileges using<script> tags but then you take the responsibility for registering them as part of your package.

  • Use the <createfunction><createtable><createtrigger>, and<createview> tags responsibly. The scripts named with these tags should do nothing more than the tag names imply - create functions with a particular name, create a single table, create the triggers on a particular table, and create a single view respectively. The only exception to this rule is that these scripts should do the extra work to make them useful, such as drop a view before recreating it or perhaps create a trigger function before creating the trigger itself.

First Release

Creating a package for the first time is relatively simple because you have no backwards compatibility issues. This will certainly crop up if you need to upgrade your package contents in the future. For now, however, let's just talk about first-time package creation.

So, how do you start? Like with the xTuple ERP upgrades, you need to collect individual files, each of which contains one piece of data or performs one basic task. Here are the kinds of things you might want to include in your package:

  • Screen definitions to add to xTuple ERP.
  • Custom commands to provide access to your screens.
  • Privileges to limit access to your screens.
  • Scripts to run these screens or to supplement existing xTuple ERP windows.
  • MetaSQL statements that can be shared by multiple windows and reports.
  • Report definitions.
  • Images.
  • New tables.
  • New views.
  • New stored procedures.
  • New triggers.
  • Additional rows in existing tables.

Create a directory to hold all of these pieces. It would be best to give this the same name you want your package to have. As you work on the components of your package, you should put them in this directory. xTuple recommends that you use a source code control system such as CVS or SVN to manage this directory and its components. If the package is relatively small then you can put all of the pieces directly into this directory. If the package grows too large it might be easier to create subdirectories for QtScript files, .ui files, report definitions, etc.

Once you have a directory to hold this stuff, create an essentially empty package.xml file. Create the <package> element with its idnamedeveloper, and version attributes and <pkgnotes> children to describe at a high level the package and its contents. The id attribute must match the directory name; otherwise the user will get an error when s/he tries to install your package. The version attribute can be whatever you like but it would be best to start at 0.1 or 1.0.

Use the Qt Designer embedded in xTuple ERP to generate your screen definitions. It is possible to install Qt on your own and use the whole Qt framework, but this is usually not necessary. If you do this, you must use the right version of Qt and you must install the shared library that comes with xTuple ERP in the Designer plugins directory before you start designing windows. Check the Development Environment Setup Guide to find out which version of Qt you will need. Using the wrong version of Qt will give you unusable .ui files. Add the .ui files to your package directory and add <loadappui> elements to your package.xml file as you create new ones.

As you write QtScript to drive your windows, place them in individual files as well, one for each window that they will control. The scripts and .ui files must have the same name for xTuple ERP to use them together. Add <loadappscript> elements to package.xml.

When you find that you need to add custom commands and privileges to your package, you can write these directly into the package.xml. See the syntax summary for details or the privilege and command examples.

You can use xTuple ERP to create and save report definitions. You can also use OpenRPT utilities to build and test your reports and MetaSQL definitions, then save them to your package directory. The openrpt binary has a report editor that can save reports either to a database or to a text file. The metasql application has a simple text editor you can use to create the statement as well as the ability to pass parameter values to your MetaSQL statement and execute the result against the database.

Image files can simply be copied to your directory. These can either be binary image files or uuencoded versions of those images.

Remember to explicitly add each file you create, including package.xml, to the source control system. Some, like RCS and CVS, will need to be told when you are adding binary files, such as images; others, like SVN, can figure this out on their own.

If you find that there is something you cannot add using these tags, then use the <script>, <initscript>, and <finalscript> tags to write arbitrary SQL statements. The difference between these tags is the order in which they are run by the Updater.

Subsequent Releases

Just like any other software product, you'll probably have to update your extension package at some point. You might need to add new functionality or change it to run with newer versions of xTuple ERP. When you create update packages you might have to stretch some of the guidelines a bit. For example, you might want to create a package that upgrades existing old versions of your package but installs fresh if the database does not have a copy of your package already installed.

To illustrate some of the potential problems, let's talk about a package that contains a simple form, script, and table. In version 1.0 of your package the table had two columns and for version 1.1 you realize that you need to add a third column. The form and script don't cause any issues because the Updater just replaces the old 1.0 versions with the new 1.1 versions of these. They're just data stored in a table and the Updater knows to do an UPDATE rather than an INSERT for these.

The table change, however, will cause problems. For new installations of your package you want the table to be CREATEd while for existing installations you want it to be ALTERed. The <createtable> line in package.xml line can be exactly the same in both versions 1.0 and 1.1 of package.xml:

<createtable name="myTable" file="myTable.sql" />

In version 1.0 you might have written myTable.sql like so:

-- myTable.sql script for package version 1.0
CREATE TABLE myTable (
    my_id     SERIAL,
    my_value  INTEGER
);
REVOKE ALL ON TABLE myTable FROM PUBLIC;
GRANT  ALL ON TABLE myTable TO   admin;
GRANT  ALL ON TABLE myTable TO   GROUP xtrole;

REVOKE ALL ON TABLE myTable_myTable_id_seq FROM PUBLIC;
GRANT  ALL ON TABLE myTable_myTable_id_seq TO   admin;
GRANT  ALL ON TABLE myTable_myTable_id_seq TO   GROUP xtrole;

This creates the table with two columns and makes sure that all xTuple ERP users but only xTuple ERP users can see the table and the sequence that controls the id column. The trick for the upgrade is to write myTable.sql so it can handle both the case where the table already exists and where the table does not. It might look something like this:

CREATE FUNCTION createMyTable() RETURNS INTEGER AS $$
DECLARE
  _statement TEXT := '';
  _version   TEXT := '';
BEGIN
  IF (EXISTS(SELECT relname
               FROM pg_class, pg_namespace
              WHERE relname='mytable'
                AND relnamespace=pg_namespace.oid
                AND nspname='mypackage')) THEN
    _statement = 'ALTER TABLE myTable ADD my_comment TEXT;';
  ELSE
    _statement = 'CREATE TABLE myTable (' ||
                 '  my_id      SERIAL,'   ||
                 '  my_value   INTEGER,'  ||
                 '  my_comment TEXT'      ||
                 ');';
  END IF;

  EXECUTE _statement;

  RETURN 0;
END;
$$ LANGUAGE 'plpgsql';

SELECT createMyTable();
DROP FUNCTION createMyTable();

REVOKE ALL ON TABLE myTable FROM PUBLIC;
GRANT  ALL ON TABLE myTable TO   admin;
GRANT  ALL ON TABLE myTable TO   GROUP xtrole;

REVOKE ALL ON TABLE myTable_myTable_id_seq FROM PUBLIC;
GRANT  ALL ON TABLE myTable_myTable_id_seq TO   admin;
GRANT  ALL ON TABLE myTable_myTable_id_seq TO   GROUP xtrole;

Note that this revised script does exactly one thing as its net effect: It ensures that the table exists with the desired format. If the table already exists, then it gets updated. If the table does not exist then it gets created. A stored procedure is created to do this work, gets executed, and is finally dropped.

Dependencies

If you followed the advice above and split your add-on into several packages, where one package depends on another to work properly, you'll have to make sure that the Updater recognizes this dependency. The package.xml supports a special <prerequisite> description for tracking dependencies. To pass the prerequisite check, the package described in the <dependson> element of the <prerequisite> must be installed:

  • There must be a package with the required name.
  • If the <dependson> element has a version attribute then that exact version of that package must be installed.

  • If the developer attribute is given then the package must match the developer as well.

Here is a sample dependency prerequisite specification:

...
 <prerequisite type="dependency" >
  <dependson name="parentpkg" version="1.1" developer="myCompany"    />
  <message>
    parentpkg, written by myCompany, contains features my package needs.
    Specific aspects of my package require version 1.1 of parentpkg.
  </message>
 </prerequisite>
...

You can learn more about prerequisites by looking at some examples or at the package syntax.

Internal Details

Loading Sequence

When the Updater first opens a package the following things happen:

  • It looks for a package description in a file named package.xml. If it finds more than one, then it reports an error. If none is found, then it reports an error. If the package description is found in contents.xml (the predecessor of package.xml) then a warning is written to the debugging output.

  • Then the Updater parses package.xml. If the syntax of the file is incorrect, if the Updater version does not match the requirements of the package, or if there was a problem processing one of the element descriptions, messages are written to the Updater window. Fatal messages are written in red, warnings in orange. If any of the errors are fatal then the Updater stops processing the package description after all of the messages are written.

  • If there were no fatal errors processing the package description, the Updater checks the prerequisites in the order listed in the package description. License Agreement prerequisites are displayed in a dialog window. If one or more of the prerequisites fails then processing stops. If they all pass then the user is advised to back up the database and the Start Update button is enabled.

At this point the user has the option to process the update, open a different package, or quit. If s/he chooses to process the update, then s/he should click the Start Update button. When the Start Update button is pressed the Updater does the following in this order:

  • A transaction is started.
  • If this package is not named with a name attribute, then it is treated as a database upgrade and the contents are written to the public database schema. If the package is named, then a new schema is created in the database. If there isn't already a schema with this package's name, a record is written to the pkgheadtable for it, and the schema search path for the Updater is reset with the package schema in front.

  • Elements are processed in a specific order:
    • <initscript>

    • <loadpriv>

    • <script>

    • <createfunction>

    • <createtable>

    • <createtrigger>

    • <createview>

    • <loadmetasql>

    • <loadreport>

    • <loadappui>

    • <loadappscript>

    • <loadcmd>

    • <loadimage>

    • <prerequisite> with dependson attributes - these are stored in the pkgdep tabl

    • <finalscript>

  • In the normal case any error immediately results in an error message and the update is immediately rolled back. If any errors were ignored during processing (see package.xml Reference) then the user is prompted to choose whether to accept the update or roll it back.

  • If no errors occurred or the user chose to ignore them, the update is committed and the schema path is restored to its default. At this point the user can either load a new update package or quit.

Different types of element are loaded in the order listed above. Within a single type, elements are loaded in the order they are found in the package.xml file.

It is important to note the differences between named and unnamed packages. They are distinguished by whether the <package> element in the package description has a name attribute or not. If it has a name then the expectation is that this is an application extension. If the package does not have a name then the expectation is that this is a database upgrade. Database upgrades are all processed with the default schema path, so changes are applied to the public schema unless the individual script explicitly changes this. Add-on packages are stored in their own schemas. As mentioned above the Updater creates a new schema named after the package and changes the run-time schema path. The Updater creates several tables in this new schema to hold schema-specific data:

  • pkgmetasql

  • pkgreport

  • pkguiform

  • pkgscript

  • pkgcmd

  • pkgcmdarg

  • pkgimage

These are all created as child tables of tables in the public schema, which makes for seamless integration from the application users' perspective. It also allows administrators to disable or enable packages - internally this is done by breaking or re-establishing the inheritance relationships.

package.xml Reference

Syntax of package.xml

Here is an approximate XML Schema representation of the package file format:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<xsd:simpleType name="fullversionnumber">
  <xsd:restriction base="xsd:string">
    <xsd:pattern value="\d+.\d+.\d+((wip|alpha|beta|rc)(\d)?)?"     />
  </xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="onerror">
  <xsd:restriction base="xsd:string">
    <xsd:pattern value="(Default|Stop|Prompt|Ignore)"     />
  </xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="grade">
  <xsd:restriction base="xsd:string">
    <xsd:pattern value="(highest|\d+|lowest)"     />
  </xsd:restriction>
</xsd:simpleType>

<xsd:simpleType name="enabled">
  <xsd:restriction base="xsd:string">
    <xsd:pattern value="(t(rue)?|f(alse)?|T(RUE)?|F(ALSE)?)"     />
  </xsd:restriction>
</xsd:simpleType>

<xsd:complexType name="dbobj" mixed="true" >
  <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment -->
    <xsd:sequence>
      <xsd:attribute name="name"    use="required" />
      <xsd:attribute name="file"    use="required" />
      <xsd:attribute name="schema   use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) -->
      <xsd:attribute name="onerror" use="optional" type="onerror" />
      <xsd:element minOccurs="0"     /> <!-- text of comment -->
    </xsd:sequence>
  </xsd:complexType>
</xsd:complexType>

<xsd:element name="package">
  <xsd:sequence>
    <xsd:attribute name="id"        use="required"     />
    <xsd:attribute name="name"      use="optional"     />                <!-- marks package as extension if present -->
    <xsd:attribute name="developer" use="optional"     />
    <xsd:attribute name="version"   use="optional" type="xsd:decimal" /> <!-- required for extension packages -->
    <xsd:attribute name="descrip"   use="optional"     />
    <xsd:attribute name="updater"   use="optional" type="fullversionnumber" />

    <xsd:element name="pkgnotes" minOccurs="0" maxOccurs="unbounded"     />

    <xsd:element name="prerequisite">
      <xsd:sequence>
        <xsd:attribute name="name"  use="optional" \>
        <xsd:attribute name="type" type="(query|license|dependency)"     />
        <xsd:element name="query"     />
        <xsd:element name="dependson">
          <xsd:sequence>
            <xsd:attribute name="name" \>
            <xsd:attribute name="version" type="decimal" \>
            <xsd:attribute name="developer" \>
          </xsd:sequence>
        </xsd:element>
        <xsd:element name="message"     />
        <xsd:element name="providedby" >
          <xsd:sequence>
            <xsd:attribute name="name"     />
          </xsd:sequence>
        </xsd:element>
      </xsd:sequence>
    </xsd:element>

    <xsd:element name="initscript" minOccurs="0" maxOccurs="unbounded">
      <xsd:sequence>
        <xsd:attribute name="file"    use="optional"     /> <!-- name and file are equiv, one is required -->
        <xsd:attribute name="name"    use="optional"     /> <!-- file takes precedence   -->
        <xsd:attribute name="onerror" use="optional" type="onerror"     />
      </xsd:sequence>
    </xsd:element>

    <xsd:element name="script" minOccurs="0" maxOccurs="unbounded">
      <xsd:sequence>
        <xsd:attribute name="file"    use="optional"     /> <!-- name and file are equiv, one is required -->
        <xsd:attribute name="name"    use="optional"     /> <!-- file takes precedence   -->
        <xsd:attribute name="onerror" use="optional" type="onerror"     />
      </xsd:sequence>
    </xsd:element>

    <xsd:element name="finalscript" minOccurs="0" maxOccurs="unbounded">
      <xsd:sequence>
        <xsd:attribute name="file"    use="optional"     /> <!-- name and file are equiv, one is required -->
        <xsd:attribute name="name"    use="optional"     /> <!-- file takes precedence   -->
        <xsd:attribute name="onerror" use="optional" type="onerror"     />
      </xsd:sequence>
    </xsd:element>
    <xsd:element name="createfunction" minOccurs="0" maxOccurs="unbounded" type="dbobj" />
    <xsd:element name="createtable"    minOccurs="0" maxOccurs="unbounded" type="dbobj" />
    <xsd:element name="createtrigger"  minOccurs="0" maxOccurs="unbounded" type="dbobj" />
    <xsd:element name="createview"     minOccurs="0" maxOccurs="unbounded" type="dbobj" />

    <xsd:element name="loadmetasql" minOccurs="0" maxOccurs="unbounded">
      <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment -->
        <xsd:sequence>
          <xsd:attribute name="file"    use="required"     />
          <xsd:attribute name="name"    use="optional"     />
          <xsd:attribute name="group"   use="optional"     />
          <xsd:attribute name="schema   use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) -->
          <xsd:attribute name="onerror" use="optional" type="onerror"     />
          <xsd:attribute name="schema      use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) -->
        </xsd:sequence>
</xsd:complexType> </xsd:element> <xsd:element name="loadpriv" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment --> <xsd:sequence> <xsd:attribute name="name" use="required" /> <xsd:attribute name="module" use="optional" /> <!-- defaults to Custom --> <xsd:attribute name="onerror" use="optional" type="onerror" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) --> </xsd:sequence> </xsd:element> <xsd:element name="loadreport" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment --> <xsd:sequence> <xsd:attribute name="file" use="required" /> <xsd:attribute name="name" use="optional" /> <xsd:attribute name="onerror" use="optional" type="onerror" /> <xsd:attribute name="grade" use="optional" type="grade" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) --> </xsd:sequence> </xsd:complexType>
</xsd:element> <xsd:element name="loadappui" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment --> <xsd:sequence> <xsd:attribute name="file" use="required" /> <xsd:attribute name="name" use="optional" /> <xsd:attribute name="onerror" use="optional" type="onerror" /> <xsd:attribute name="order" use="optional" type="grade" /> <xsd:attribute name="enabled" use="optional" type="enabled" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) --> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="loadappscript" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment --> <xsd:sequence> <xsd:attribute name="file" use="required" /> <xsd:attribute name="name" use="required" /> <xsd:attribute name="onerror" use="optional" type="onerror" /> <xsd:attribute name="order" use="optional" type="grade" /> <xsd:attribute name="enabled" use="optional" type="enabled" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) --> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="loadcmd" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute or an <arg> child it's a comment --> <xsd:sequence> <xsd:attribute name="name" use="required" /> <xsd:attribute name="title" use="required" /> <xsd:attribute name="executable" use="required" /> <xsd:attribute name="module" use="required" /> <xsd:attribute name="privname" use="optional" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) --> <xsd:attribute name="onerror" use="optional" type="onerror" />
<xsd:element name="arg" minOccurs="0" maxOccurs="unbounded" > <xsd:sequence> <xsd:attribute name="value" use="required" /> <xsd:sequence> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="loadimage" minOccurs="0" maxOccurs="unbounded"> <xsd:complexType mixed="true"> <!-- if it isn't an attribute it's a comment --> <xsd:sequence> <xsd:attribute name="file" use="required" /> <xsd:attribute name="name" use="required" /> <xsd:attribute name="onerror" use="optional" type="onerror" /> <xsd:attribute name="schema use="optional" /> <!-- only use if you need to place the obj outside the package schema (public for upgrades) -->
</xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:element> </xsd:schema>

Notice the attribute onerror on most of the elements in the package description. This attribute lets you specify whether an error which occurs during processing should stop the update immediately, be ignored, be automatically retried, or if the Updater should ask the user which of these paths to take. The default is stop, which causes the upgrade to roll back. Be very careful if you decide to override this default for several reasons:

  • You may confuse the user by asking a question s/he does not know how to answer.
  • You risk corrupting the data if you allow the Updater to ignore the failure, either by setting the behavior to ignore or by letting the user decide with prompt.

  • You risk locking the database for a long time, as the entire update is run in a single transaction. This transaction is held open while waiting for the user to respond toprompts. If any failures were ignored, the Updater also puts up a dialog at the end of the update processing asking if s/he really wants to ignore these errors, which also holds the transaction open.

Special notes on report definitions

The name of the report in the <loadreport> element's name attribute is overridden by the content of the report file's <name> element. The description of the report in the <loadreport>'s comment text is overridden by the content of the report file's <description> element.

If a report's grade attribute is set to 'lowest', then the grade is set to the smallest unused grade for reports with this name. if the grade attribute is set to 'highest', then the grade is set to one greater than the highest used report_grade for reports with this name.

Special notes on MetaSQL definitions

The group, name, and comments assigned to a MetaSQL statement in the <loadmetasql> element are overridden by the contents of the loaded file. If the file has a comment on a line by itself that starts with the word group: (case insensitive) then this is the group value used for the MetaSQL statement. If the file has a name:comment then this is the name value used for the MetaSQL statement. If the file has a notes: comment then this is the comment value used for the MetaSQL statement. All comment lines following the notes: comment are included. For example:

   -- Group:display
--naMe:   fillList
SELECT * FROM cohead WHERE cohead_id = <? value("cohead_id") ?>;
 -- NOTES: This
-- is a
          --strange comment

will create an entry in the metasql table like this:

metasql_group

display

metqsql_name

fillList

metasql_notes

This is a strange comment

Special notes on.UI files

The order attribute of <loadappui> elements is handled much like grades are for reports. The difference is that uiforms may have multiple rows with the same order, so there is no special processing to avoid duplicates.

If the .ui file named in the file attribute does not have <ui> as the document's root element, then the load fails. The value given to the name attribute is overridden by the the name of the first <class> element in the .ui file.

Special notes on Application Scripts

The order attribute of <loadappscript> elements is handled the same as for <loadappui> elements. Application scripts must be given the same name as the  window to which they apply unless they have a special purpose, such as initializing the menu system (initMenu) or being included by other scripts.

Special notes on Images

The Updater looks at the first few characters of image files before loading them into the database. If an individual file is a raw graphics file, then it is uuencoded before loading into the db. If the file is already uuencoded, then it gets loaded as is. The Updater supports the same image formats as the main xTuple ERP application does, which in turn is dependent on how Qt was built. See the Development Environment Setup Guide for Qt configuration options, including supported graphics formats.

Database Structure

The xTuple ERP application uses primarily the public schema in the application database. Add-on packages are written to their own schemas, named after the package itself. When the Updater scans package.xml it checks to see if this is a named add-on package or not. If it is an add-on, then it creates a schema to hold the package. If a schema with that name doesn't already exist, then it changes the search path to put this package-specific schema first. Any database objects that get created by the update, like tables and views, are then created in this package schema rather than in public.

The Updater uses a number of tables in the database. Most tables are only used by particular features of the Updater. As long as these tables exist, the Updater itself will work. If you don't use a certain feature then the Updater won't use that particular table. Here is a list of tables directly accessed by the Updater:

Table Used By Comments
pkghead all parts only for add-on packages
cmd * <loadcmd> has sequence cmd_cmd_id_seq
cmdarg * <loadcmd><arg> has sequence cmdarg_cmdarg_id_seq
image * <loadimage> has sequence image_image_id_seq
metasql * <loadmetasql> has sequence metasql_metasql_id_seq and accessed through a stored proceduresavemetasql(TEXT, TEXT, TEXT, TEXT, BOOLEAN) RETURNS INTEGER
priv * <loadpriv> has sequence priv_priv_id_seq
report * <loadreport> has sequence report_report_id_seq
script * <loadappscript> has sequence script_script_id_seq
uiform * <loadappui> has sequence uiform_uiform_id_seq

Historical note: The first few versions of xTuple ERP and the Updater that enabled extension package loading had a pkgitem table. This table is still in xTuple databases but is deprecated and no longer actively maintained.

All of the tables marked with an asterisk (*) have the following in common:

  • The Updater creates a special child table called pkgparenttable in the package's schema for add-on packages. In version 2.0.0 of the Updater, all of these child tables are created as soon as the Updater creates the schema for the package. Hence parent tables with the proper names must be defined in thepublic schema.

  • The Updater creates two triggers on each of these package-specific child tables, one using the function _pkgparenttablebeforetrigger and the other using_pkgparenttablealtertrigger.

  • The sequence used by the parent tables are shared with the child tables, which avoids having multiple records with the same id in the parent and child tables.

There are two reasons for using schemas to hold add-on packages:

  • Schemas isolate the contents of one package from another, so changes to one package should not affect changes to another.
  • By using child tables in the add-on package schema it becomes simple to disable packages  all that is required at the database level is detaching the child tables from their parents. This prevents the contents of the children from being shown when someone selects from the parent, so they effectively disappear. Re-enabling a package requires re-attaching the children to the parents.

 

up
111 users have voted.