diff --git a/common/source/java/ch/systemsx/cisd/common/db/DBRestrictionParser.java b/common/source/java/ch/systemsx/cisd/common/db/DBRestrictionParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..f30b28fdbeb6676e6660f36dbe822b20b26b6945
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/db/DBRestrictionParser.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.db;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+
+/**
+ * A parser for SQL92 data definition language scripts. The result are columns lengths and checked constraints.
+ * 
+ * @author Bernd Rinn
+ */
+public class DBRestrictionParser
+{
+    private static final String CREATE_DOMAIN_PREFIX = "create domain ";
+
+    private static final Pattern VARCHAR_PATTERN = Pattern.compile("(varchar|character varying)\\(([0-9]+)\\).*");
+
+    /** The prefix each <code>create table</code> statement starts with. */
+    private static final String CREATE_TABLE_PREFIX = "create table ";
+
+    private static final Pattern CREATE_TABLE_PATTERN =
+            Pattern.compile(CREATE_TABLE_PREFIX + "([a-z,0-9,_]+) \\((.+)\\)");
+
+    private static final Pattern NOT_NULL_TABLE_PATTERN =
+            Pattern.compile("\\w+ ((default .+ not null)|(not null)|(not null .+ default.+))");
+    
+    /** The prefix each <code>alter table</code> statement starts with (to add a constraint). */
+    private static final String ALTER_TABLE_PREFIX = "alter table ";
+
+    private static final Pattern CHECK_CONSTRAINT_PATTERN =
+            Pattern.compile(ALTER_TABLE_PREFIX
+                    + "([a-z,0-9,_]+) add constraint [a-z,0-9,_]+ check \\(([a-z,0-9,_]+) in \\((.+)\\)\\)");
+
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, DBRestrictionParser.class);
+
+    // @Private
+    final Map<String, DBTableRestrictions> tableRestrictionMap = new HashMap<String, DBTableRestrictions>();
+
+    public DBRestrictionParser(String ddlScript)
+    {
+        final List<String> normalizedDDLScript = normalize(ddlScript);
+        final Map<String, Integer> domains = parseDomains(normalizedDDLScript);
+        parseColumnLength(normalizedDDLScript, domains);
+        parserCheckedConstraints(normalizedDDLScript);
+    }
+
+    // @Private
+    static List<String> normalize(String ddlScript)
+    {
+        final List<String> list = new ArrayList<String>();
+        final SQLCommandTokenizer normalizer = new SQLCommandTokenizer(ddlScript);
+        String command;
+        do
+        {
+            command = normalizer.getNextCommand();
+            if (command != null)
+            {
+                list.add(command);
+            }
+        } while (command != null);
+        return list;
+    }
+
+    // @Private
+    static Map<String, Integer> parseDomains(List<String> ddlScript)
+    {
+        final Map<String, Integer> domains = new HashMap<String, Integer>();
+        for (String line : ddlScript)
+        {
+            if (line.startsWith(CREATE_DOMAIN_PREFIX))
+            {
+                String domainDefinition = line.substring(CREATE_DOMAIN_PREFIX.length());
+                int indexOfAS = domainDefinition.indexOf("as");
+                if (indexOfAS < 0)
+                {
+                    operationLog.warn("line \"" + line
+                            + "\" starts like a domain definition, but key word 'AS' is missing.");
+                    continue;
+                }
+                String domainName = domainDefinition.substring(0, indexOfAS).trim();
+                domainDefinition = domainDefinition.substring(indexOfAS + 2).trim();
+                final Matcher varCharMatcher = VARCHAR_PATTERN.matcher(domainDefinition);
+                if (varCharMatcher.matches())
+                {
+                    domains.put(domainName, Integer.parseInt(varCharMatcher.group(2)));
+                }
+            }
+        }
+
+        return domains;
+    }
+
+    private void parseColumnLength(List<String> ddlScript, Map<String, Integer> domains)
+    {
+        for (String line : ddlScript)
+        {
+            final Matcher createTableMatcher = CREATE_TABLE_PATTERN.matcher(line);
+            if (createTableMatcher.matches())
+            {
+                final String tableName = createTableMatcher.group(1);
+                final String tableDefinition = createTableMatcher.group(2);
+                final String[] columnDefinitions = StringUtils.split(tableDefinition, ',');
+                for (String columnDefinition : columnDefinitions)
+                {
+                    parseColumnDefinition(columnDefinition, tableName, domains);
+                }
+            }
+        }
+    }
+
+    private void parseColumnDefinition(String columnDefinition, final String tableName, Map<String, Integer> domains)
+            throws NumberFormatException
+    {
+        if (columnDefinition.startsWith("constraint "))
+        {
+            return;
+        }
+        int indexOfFirstSpace = columnDefinition.indexOf(' ');
+        if (indexOfFirstSpace < 0)
+        {
+            operationLog.warn("Invalid column definition \"" + columnDefinition + "\" for table " + tableName);
+            return;
+        }
+        String columnName = columnDefinition.substring(0, indexOfFirstSpace).trim();
+        if (columnName.startsWith("\"") && columnName.endsWith("\""))
+        {
+            columnName = columnName.substring(1, columnName.length() - 1);
+        }
+        final String typeDefinition = columnDefinition.substring(indexOfFirstSpace).trim();
+        final Matcher varCharMatcher = VARCHAR_PATTERN.matcher(typeDefinition);
+        if (varCharMatcher.matches())
+        {
+            getTableRestrictions(tableName).columnLengthMap.put(columnName, Integer.parseInt(varCharMatcher
+                    .group(2)));
+        } else
+        {
+            final Integer domainLength = domains.get(StringUtils.split(typeDefinition, ' ')[0]);
+            if (domainLength != null)
+            {
+                getTableRestrictions(tableName).columnLengthMap.put(columnName, domainLength);
+            }
+        }
+        
+        if (NOT_NULL_TABLE_PATTERN.matcher(typeDefinition).matches())
+        {
+            getTableRestrictions(tableName).notNullSet.add(columnName);
+        }
+    }
+
+    private void parserCheckedConstraints(List<String> ddlScript)
+    {
+        for (String line : ddlScript)
+        {
+            final Matcher checkedConstraintMatcher = CHECK_CONSTRAINT_PATTERN.matcher(line);
+            if (checkedConstraintMatcher.matches())
+            {
+                final String tableName = checkedConstraintMatcher.group(1);
+                final String columnName = checkedConstraintMatcher.group(2);
+                final String alternativesStr = checkedConstraintMatcher.group(3);
+                final String[] alternatives = StringUtils.split(alternativesStr, ',');
+                final Set<String> alternativeSet = new HashSet<String>();
+                for (String alternative : alternatives)
+                {
+                    if (alternative.charAt(0) != '\'' || alternative.charAt(alternative.length() - 1) != '\'')
+                    {
+                        operationLog.warn("Invalid alternatives definition \"" + alternative + "\" for column "
+                                + columnName + " of table " + tableName);
+                        continue;
+                    }
+                    alternativeSet.add(alternative.substring(1, alternative.length() - 1));
+                }
+                getTableRestrictions(tableName).checkedConstraintsMap.put(columnName, alternativeSet);
+            }
+        }
+    }
+
+    /**
+     * @return The table restrictions of <var>tableName</var>
+     */
+    // @Private
+    DBTableRestrictions getTableRestrictions(String tableName)
+    {
+        DBTableRestrictions table = tableRestrictionMap.get(tableName);
+        if (table == null)
+        {
+            table = new DBTableRestrictions();
+            tableRestrictionMap.put(tableName, table);
+        }
+        return table;
+    }
+
+    public Map<String, DBTableRestrictions> getDBRestrictions()
+    {
+        return Collections.unmodifiableMap(tableRestrictionMap);
+    }
+    
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/db/DBTableRestrictions.java b/common/source/java/ch/systemsx/cisd/common/db/DBTableRestrictions.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae1d5c5a72f94339ed11c22f695200455b799a0c
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/db/DBTableRestrictions.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.db;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class that holds the restrictions put upon columns in a database table.
+ * 
+ * @author Bernd Rinn
+ */
+public class DBTableRestrictions
+{
+    final Map<String, Integer> columnLengthMap = new HashMap<String, Integer>();
+
+    final Map<String, Set<String>> checkedConstraintsMap = new HashMap<String, Set<String>>();
+
+    final Set<String> notNullSet = new HashSet<String>();
+
+    public int getLength(String columnName)
+    {
+        final Integer columnLength = columnLengthMap.get(columnName);
+        assert columnLength != null : "Illegal column '" + columnName +"'.";
+        return columnLength;
+    }
+
+    public Set<String> tryGetCheckedConstaint(String columnName)
+    {
+        final Set<String> checkedConstraint = checkedConstraintsMap.get(columnName);
+        return (checkedConstraint == null) ? null : Collections.unmodifiableSet(checkedConstraint);
+    }
+
+    public boolean hasNotNullConstraint(String columnName)
+    {
+        return notNullSet.contains(columnName);
+    }
+}
\ No newline at end of file
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionParserTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionParserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ae9910cdaa149188392644cf1197eee6ac51b65
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionParserTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.db;
+
+import static org.testng.AssertJUnit.*;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogInitializer;
+import ch.systemsx.cisd.common.logging.LogMonitoringAppender;
+import ch.systemsx.cisd.common.utilities.FileUtilities;
+
+/**
+ * Test cases for the @{link DBREstrictionParser}.
+ * 
+ * @author Bernd Rinn
+ */
+public class DBRestrictionParserTest
+{
+
+    private String sqlScript;
+
+    @BeforeClass
+    public void setup()
+    {
+        LogInitializer.init();
+        sqlScript =
+                FileUtilities.loadToString(new File(
+                        "sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionsTest.sql"));
+        assert sqlScript != null;
+    }
+
+    @Test
+    public void testNormalize()
+    {
+        final List<String> normalizedList = DBRestrictionParser.normalize(" a  1 ;; B\t2;\n\nC; \n--D\n E ");
+        assertEquals(Arrays.asList("a 1", "b 2", "c", "e"), normalizedList);
+    }
+
+    @Test
+    public void testGetDomains()
+    {
+        String invalidDomainStatement = "create domain bla for varchar(0)";
+        final List<String> domainScript =
+                Arrays.asList("create table sometable", "create domain user_id as varchar(15)",
+                        invalidDomainStatement, "create domain code as varchar(8)",
+                        "create domain description_80 as varchar(81)");
+        final LogMonitoringAppender appender =
+                LogMonitoringAppender.addAppender(LogCategory.OPERATION, "line \"" + invalidDomainStatement
+                        + "\" starts like a domain definition, but key word 'AS' is missing.");
+        try
+        {
+            final Map<String, Integer> domains = DBRestrictionParser.parseDomains(domainScript);
+            appender.verifyLogHasHappened();
+            final Map<String, Integer> expectedDomains = new HashMap<String, Integer>();
+            expectedDomains.put("user_id", 15);
+            expectedDomains.put("code", 8);
+            expectedDomains.put("description_80", 81);
+            assertEquals(expectedDomains, domains);
+        } finally
+        {
+            LogMonitoringAppender.removeAppender(appender);
+        }
+    }
+
+    @Test
+    public void testDefaultKeywordInDomain()
+    {
+        final List<String> domainScript =
+            Arrays.asList("create domain vc22 as varchar(22) default 'nothing special'");
+        
+        final LogMonitoringAppender appender = LogMonitoringAppender.addAppender(LogCategory.OPERATION, "ill-formed");
+        try
+        {
+            final Map<String, Integer> domains = DBRestrictionParser.parseDomains(domainScript);
+            appender.verifyLogHasNotHappened();
+            assertNotNull(domains.get("vc22"));
+            assertEquals(22, domains.get("vc22").intValue());
+        } finally
+        {
+            LogMonitoringAppender.removeAppender(appender);
+        }
+    }
+    
+    @Test
+    public void testDoublePrecisionInDomain()
+    {
+        final List<String> domainScript =
+            Arrays.asList("create domain dp as double precision");
+        
+        final LogMonitoringAppender appender = LogMonitoringAppender.addAppender(LogCategory.OPERATION, "ill-formed");
+        try
+        {
+            final Map<String, Integer> domains = DBRestrictionParser.parseDomains(domainScript);
+            appender.verifyLogHasNotHappened();
+            assertTrue(domains.isEmpty());
+        } finally
+        {
+            LogMonitoringAppender.removeAppender(appender);
+        }
+    }
+    
+    @Test
+    public void testDoublePrecisionAndDefaultInDomain()
+    {
+        final List<String> domainScript =
+            Arrays.asList("create domain dp as double precision default 3.14159");
+        
+        final LogMonitoringAppender appender = LogMonitoringAppender.addAppender(LogCategory.OPERATION, "ill-formed");
+        try
+        {
+            final Map<String, Integer> domains = DBRestrictionParser.parseDomains(domainScript);
+            appender.verifyLogHasNotHappened();
+            assertTrue(domains.isEmpty());
+        } finally
+        {
+            LogMonitoringAppender.removeAppender(appender);
+        }
+    }
+    
+    @Test
+    public void testColumnLengths()
+    {
+        final DBRestrictionParser parser = new DBRestrictionParser(sqlScript);
+
+        assertEquals(10, parser.getTableRestrictions("contacts").getLength("cnta_type"));
+        assertEquals(30, parser.getTableRestrictions("contacts").getLength("firstname"));
+        assertEquals(1, parser.getTableRestrictions("contacts").getLength("midinitial"));
+        assertEquals(30, parser.getTableRestrictions("contacts").getLength("lastname"));
+        assertEquals(50, parser.getTableRestrictions("contacts").getLength("email"));
+        assertEquals(15, parser.getTableRestrictions("contacts").getLength("user_id"));
+
+        assertEquals(8, parser.getTableRestrictions("material_types").getLength("code"));
+        assertEquals(80, parser.getTableRestrictions("material_types").getLength("description"));
+
+        assertEquals(50, parser.getTableRestrictions("materials").getLength("name"));
+        assertEquals(4, parser.getTableRestrictions("materials").getLength("mate_sub_type"));
+    }
+
+    @Test(expectedExceptions = AssertionError.class)
+    public void testInvalidTable()
+    {
+        final DBRestrictionParser parser = new DBRestrictionParser("");
+        assertEquals(Integer.MAX_VALUE, parser.getTableRestrictions("doesnotexit").getLength("doesnotexist"));
+    }
+
+    @Test(expectedExceptions = AssertionError.class)
+    public void testInvalidColumn()
+    {
+        final DBRestrictionParser parser = new DBRestrictionParser("create table tab (a integer, b varchar(1))");
+        assertEquals(Integer.MAX_VALUE, parser.getTableRestrictions("tab").getLength("doesnotexist"));
+    }
+
+    @Test
+    public void testCheckedConstraints()
+    {
+        final DBRestrictionParser parser = new DBRestrictionParser(sqlScript);
+
+        assertEquals(new HashSet<String>(Arrays.asList("PERS", "ORGA")), parser.getTableRestrictions("contacts")
+                .tryGetCheckedConstaint("cnta_type"));
+        assertEquals(new HashSet<String>(Arrays.asList("STOB", "MATE")), parser.getTableRestrictions("materials")
+                .tryGetCheckedConstaint("mate_sub_type"));
+    }
+
+}
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionsTest.sql b/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionsTest.sql
new file mode 100644
index 0000000000000000000000000000000000000000..51d31d1cbf9f9174117d9f69f902ec9f469e0c20
--- /dev/null
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/db/DBRestrictionsTest.sql
@@ -0,0 +1,59 @@
+-- D:\DDL\postgresql\3V_LIMS_Version_1.sql
+--
+-- Generated for ANSI SQL92 on Thu May 10  15:50:21 2007 by Server Generator 10.1.2.6.18
+
+
+ CREATE DOMAIN DESCRIPTION_80 AS VARCHAR(80);
+CREATE DOMAIN USER_ID AS VARCHAR(15);
+CREATE DOMAIN TECH_ID AS INTEGER;
+CREATE DOMAIN OBJECT_NAME AS VARCHAR(50);
+CREATE DOMAIN CODE AS VARCHAR(8);
+CREATE DOMAIN DESCRIPTION_250 AS VARCHAR(250);
+CREATE TABLE EXPERIMENT_TYPES  (CODE CODE NOT NULL,DESCRIPTION 
+DESCRIPTION_80 NOT NULL) ;
+CREATE TABLE CONTACTS  (ID TECH_ID NOT NULL,CNTA_TYPE VARCHAR(10) DEFAULT 'PERS' NOT NULL,FIRSTNAME VARCHAR(30),MIDINITIAL VARCHAR(1),LASTNAME VARCHAR(30),EMAIL VARCHAR(50),USER_ID USER_ID,CNTA_ID_ORGANIZATION TECH_ID,ORGANIZATION_NAME OBJECT_NAME,DESCRIPTION DESCRIPTION_80) ;
+CREATE TABLE MATERIAL_TYPES  (CODE CODE NOT NULL,"DESCRIPTION" DESCRIPTION_80 NOT NULL) ;
+CREATE TABLE MATERIALS  (ID TECH_ID NOT NULL,NAME OBJECT_NAME,MATE_SUB_TYPE VARCHAR(4) NOT NULL DEFAULT 'MATE',MATY_CODE CODE NOT NULL,DESCRIPTION DESCRIPTION_250) ;
+CREATE TABLE EXPERIMENTS  (
+    ID TECH_ID NOT NULL,
+    NAME OBJECT_NAME,
+    CNTA_ID_REGISTERER TECH_ID NOT NULL,
+    MATE_ID_STUDY_OBJECT TECH_ID NOT NULL,
+    EXTY_CODE CODE NOT NULL,
+    REGISTRATION_DATE DATE,
+    DESCRIPTION DESCRIPTION_250,
+    CONSTRAINT data_samp_arc_ck CHECK ((samp_id_acquired_from IS NOT NULL) AND (samp_id_derived_from IS NULL));
+);
+
+ALTER TABLE EXPERIMENT_TYPES ADD CONSTRAINT EXTY_PK PRIMARY KEY(CODE);
+ALTER TABLE CONTACTS ADD CONSTRAINT CNTA_PK PRIMARY KEY(ID);
+ALTER TABLE MATERIAL_TYPES ADD CONSTRAINT MATY_PK PRIMARY KEY(CODE);
+ALTER TABLE MATERIALS ADD CONSTRAINT MATE_PK PRIMARY KEY(ID);
+ALTER TABLE EXPERIMENTS ADD CONSTRAINT EXPE_PK PRIMARY KEY(ID);
+
+ALTER TABLE CONTACTS ADD CONSTRAINT AVCON_1178805021_CNTA__000 CHECK (CNTA_TYPE IN 
+  ('PERS', 
+  'ORGA')
+) ;
+ALTER TABLE MATERIALS ADD CONSTRAINT AVCON_1178805021_MATE__000 CHECK (MATE_SUB_TYPE IN ('MATE', 'STOB')) ;
+ALTER TABLE CONTACTS ADD CONSTRAINT CNTA_CNTA_ORGANIZATION_FK FOREIGN KEY (CNTA_ID_ORGANIZATION)REFERENCES CONTACTS(ID);
+ALTER TABLE MATERIALS ADD CONSTRAINT MATE_MATY_FK FOREIGN KEY (MATY_CODE)REFERENCES MATERIAL_TYPES(CODE);
+ALTER TABLE EXPERIMENTS ADD CONSTRAINT EXPE_CNTA_FK FOREIGN KEY (CNTA_ID_REGISTERER)REFERENCES CONTACTS(ID);
+ALTER TABLE EXPERIMENTS ADD CONSTRAINT EXPE_MATE_FK FOREIGN KEY (MATE_ID_STUDY_OBJECT)REFERENCES MATERIALS(ID);
+ALTER TABLE EXPERIMENTS ADD CONSTRAINT EXPE_EXTY_FK FOREIGN KEY (EXTY_CODE)REFERENCES EXPERIMENT_TYPES(CODE);
+
+-- D:\DDL\oracle\3V_LIMS_Version_1.sqs
+--
+-- Generated for Oracle 9i on Thu May 10  15:49:25 2007 by Server Generator 10.1.2.6.18
+ 
+-- Creating Sequence 'CONTACT_ID_SEQ'
+CREATE SEQUENCE CONTACT_ID_SEQ;
+
+-- Creating Sequence 'EXPERIMENT_ID_SEQ'
+CREATE SEQUENCE EXPERIMENT_ID_SEQ;
+
+-- Creating Sequence 'MATERIAL_ID_SEQ'
+CREATE SEQUENCE MATERIAL_ID_SEQ;
+
+
+