/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

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 com.google.security.zynamics.binnavi.Database.PostgreSQL.Functions;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.security.zynamics.binnavi.Database.CConnection;
import com.google.security.zynamics.binnavi.Database.CTableNames;
import com.google.security.zynamics.binnavi.Database.PostgreSQLProvider;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntDeleteException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntLoadDataException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntSaveDataException;
import com.google.security.zynamics.binnavi.Database.Interfaces.SQLProvider;
import com.google.security.zynamics.binnavi.Database.PostgreSQL.PostgreSQLHelpers;
import com.google.security.zynamics.binnavi.disassembly.INaviInstruction;
import com.google.security.zynamics.binnavi.disassembly.INaviModule;
import com.google.security.zynamics.binnavi.disassembly.INaviOperandTree;
import com.google.security.zynamics.binnavi.disassembly.INaviOperandTreeNode;
import com.google.security.zynamics.binnavi.disassembly.types.BaseType;
import com.google.security.zynamics.binnavi.disassembly.types.BaseTypeCategory;
import com.google.security.zynamics.binnavi.disassembly.types.RawBaseType;
import com.google.security.zynamics.binnavi.disassembly.types.RawTypeInstance;
import com.google.security.zynamics.binnavi.disassembly.types.RawTypeInstanceReference;
import com.google.security.zynamics.binnavi.disassembly.types.RawTypeMember;
import com.google.security.zynamics.binnavi.disassembly.types.RawTypeSubstitution;
import com.google.security.zynamics.binnavi.disassembly.types.TypeInstance;
import com.google.security.zynamics.binnavi.disassembly.types.TypeInstanceReference;
import com.google.security.zynamics.binnavi.disassembly.types.TypeMember;
import com.google.security.zynamics.binnavi.disassembly.types.TypeSubstitution;
import com.google.security.zynamics.zylib.disassembly.CAddress;
import com.google.security.zynamics.zylib.disassembly.IAddress;

import java.math.BigInteger;
import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

// TODO(jannewger): unify all queries to use prepared statements.
/**
 * Contains functionality to create, update or delete types and members in the database.
 */
public class PostgreSQLTypeFunctions {

  /**
   * This function appends a comment to the list of type instance comments associated to this type
   * instance.
   *
   * @param provider The provider to access the database.
   * @param moduleId The id of the module to witch the type instance is associated to.
   * @param instanceId The if of the type instance to which the comment is associated to.
   * @param commentText The comment text of the comment.
   * @param userId The id of the currently active user.
   *
   * @return The id of the comment generated by the database.
   *
   * @throws CouldntSaveDataException if the comment could not be stored in the database.
   */
  public static Integer appendTypeInstanceComment(final SQLProvider provider, final int moduleId,
      final int instanceId, final String commentText, final Integer userId)
      throws CouldntSaveDataException {

    Preconditions.checkArgument(moduleId > 0, "Error: module id must be greater then zero");
    Preconditions.checkArgument(instanceId >= 0, "instance id must be greater or equal to zero");
    Preconditions.checkNotNull(commentText, "Error: comment text argument can not be null");
    Preconditions.checkNotNull(userId, "Error: user id argument can not be null");

    final CConnection connection = provider.getConnection();

    final String function = " { ? = call append_type_instance_comment(?, ?, ?, ?) } ";

    try {
      final CallableStatement appendCommentFunction =
          connection.getConnection().prepareCall(function);

      try {
        appendCommentFunction.registerOutParameter(1, Types.INTEGER);
        appendCommentFunction.setInt(2, moduleId);
        appendCommentFunction.setInt(3, instanceId);
        appendCommentFunction.setInt(4, userId);
        appendCommentFunction.setString(5, commentText);

        appendCommentFunction.execute();

        final int commentId = appendCommentFunction.getInt(1);
        if (appendCommentFunction.wasNull()) {
          throw new CouldntSaveDataException("Error: Got an comment id of null from the database");
        }
        return commentId;
      } finally {
        appendCommentFunction.close();
      }

    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Create a new base type. The members have to be inserted separately.
   *
   * @param connection The connection to the database.
   * @param moduleId The id of the module which contains the type.
   * @param name The name of the type.
   * @param size The size of the type in bits.
   * @param childPointerTypeId The id of the the preceding base type in the pointer hierarchy, or
   *        null if the new type is a value type.
   * @param signed Specifies whether the type can represent signed values.
   */
  public static final int createType(final Connection connection,
      final int moduleId,
      final String name,
      final int size,
      final Integer childPointerTypeId,
      final boolean signed,
      final BaseTypeCategory category) throws CouldntSaveDataException {
    try {
      final String query = String.format(
          "INSERT INTO %s (module_id, id, name, size, pointer, signed, category) "
          + "VALUES (?, nextval('bn_base_types_id_seq'), ?, ?, ?, ?, ?) returning id",
          CTableNames.BASE_TYPES_TABLE);

      final PreparedStatement statement = connection.prepareStatement(query);
      try {
        statement.setInt(1, moduleId);
        statement.setString(2, name);
        statement.setInt(3, size);
        if (childPointerTypeId == null) {
          statement.setNull(4, Types.INTEGER);
        } else {
          statement.setInt(4, childPointerTypeId);
        }
        statement.setBoolean(5, signed);
        statement.setObject(6, category.toString(), Types.OTHER);
        final ResultSet resultSet = statement.executeQuery();
        if (resultSet.next()) {
          return resultSet.getInt(1);
        } else {
          throw new CouldntSaveDataException("Empty result set while inserting base type.");
        }
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Creates a new type instance in the database.
   *
   * @param connection The connection used to access the database.
   * @param moduleId The id of the module the type instance is associated to.
   * @param name The name of the type instance.
   * @param commentId The id of the comment associated with the type instance.
   * @param typeId The id of the type which is the base type for this type instance.
   * @param sectionId The id of the section where this type instance is located in.
   * @param sectionOffset The offset in the section at which the type instance starts.
   *
   * @return The id of the generated type instance in the database.
   *
   * @throws CouldntSaveDataException if the type instance could not be generated in the database.
   */
  public static int createTypeInstance(final Connection connection,
      final int moduleId,
      final String name,
      final Integer commentId,
      final int typeId,
      final int sectionId,
      final long sectionOffset) throws CouldntSaveDataException {

    Preconditions.checkNotNull(connection, "Error: connection argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be greater than zero");
    Preconditions.checkNotNull(name, "Error: name argument can not be null");
    Preconditions.checkArgument(typeId >= 0, "Error: type id must be greater than zero");
    Preconditions.checkArgument(sectionId >= 0, "Error: section id must be larger than zero");
    Preconditions.checkArgument(sectionOffset >= 0,
        "Error: section offset must be larger or equal to zero");

    try {
      final String query = " { ? = call create_type_instance(?, ?, ?, ?, ?, ?) } ";
      final CallableStatement procedure = connection.prepareCall(query);
      try {
        procedure.registerOutParameter(1, Types.INTEGER);
        procedure.setInt(2, moduleId);
        procedure.setString(3, name);
        if (commentId == null) {
          procedure.setNull(4, Types.INTEGER);
        } else {
          procedure.setInt(4, commentId);
        }
        procedure.setInt(5, typeId);
        procedure.setInt(6, sectionId);
        procedure.setLong(7, sectionOffset);
        procedure.execute();

        final int typeInstanceId = procedure.getInt(1);
        if (procedure.wasNull()) {
          throw new CouldntSaveDataException(
              "Error: the type instance id returned from the database was null");
        }
        return typeInstanceId;
      } finally {
        procedure.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Creates a new type instance reference in the database.
   *
   * @param connection The connection to the database.
   * @param moduleId The id of the module that contains the reference.
   * @param address The address of the operand referencing the type instance.
   * @param position The position of the operand within the instruction.
   * @param expressionId The id of the corresponding expression.
   * @param typeInstanceId The id of the referred type instance.
   * @throws CouldntSaveDataException Thrown if the type instance reference could not be written to
   *         the database.
   */
  public static void createTypeInstanceReference(final Connection connection,
      final int moduleId,
      final long address,
      final int position,
      final int expressionId,
      final int typeInstanceId) throws CouldntSaveDataException {

    Preconditions.checkNotNull(connection, "Error: connection argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be greater than zero");
    Preconditions.checkArgument(position >= 0, "Error: position must be larger or equal to zero");
    Preconditions.checkArgument(expressionId > 0,
        "Error: expression id must be larger or equal to zero");
    Preconditions.checkArgument(typeInstanceId >= 0,
        "Error: type instance id must be larger or equal to zero");

    final String query = " { call create_expression_type_instance(?, ?, ?, ?, ?) } ";
    try {
      final CallableStatement procedure = connection.prepareCall(query);
      try {
        procedure.setInt(1, moduleId);
        procedure.setLong(2, address);
        procedure.setInt(3, position);
        procedure.setInt(4, expressionId);
        procedure.setInt(5, typeInstanceId);
        procedure.execute();
      } finally {
        procedure.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Create a new type member in the database.
   *
   * @param connection The {@link Connection connection} to the database.
   * @param containingTypeId The id of the type where the created member is contained in.
   * @param offset The {@link Optional offset} of the type member. Can be absent if the member is a
   *        function pointer or an array.
   * @param name The name of the member.
   * @param baseTypeId The {@link BaseType basetype} id of the member.
   * @param numberOfElements {@link Optional number of elements} of the type member. Can be absent
   *        if the member is a struct or union member.
   * @param module The module which contains the member.
   * @return The id of the added type member in the database.
   * @throws CouldntSaveDataException if the member could not be saved to the database.
   */
  public static int createTypeMember(final Connection connection,
      final int containingTypeId,
      final Optional<Integer> offset,
      final String name,
      final int baseTypeId,
      final Optional<Integer> numberOfElements,
      final INaviModule module) throws CouldntSaveDataException {
    try {
      final String query = String.format(
          "INSERT INTO %s (module_id, id, name, base_type, parent_id, \"offset\", argument,"
          + " number_of_elements) VALUES (?, nextval('bn_types_id_seq'), "
          + "?, ?, ?, ?, NULL, ?) returning id", CTableNames.TYPE_MEMBERS_TABLE);

      final PreparedStatement statement = connection.prepareStatement(query);
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setString(2, name);
        statement.setInt(3, baseTypeId);
        statement.setInt(4, containingTypeId);
        if (offset.isPresent()) {
          statement.setInt(5, offset.get());
        } else {
          statement.setNull(5, Types.INTEGER);
        }
        if (numberOfElements.isPresent()) {
          statement.setInt(6, numberOfElements.get());
        } else {
          statement.setNull(6, Types.INTEGER);
        }
        final ResultSet resultSet = statement.executeQuery();
        if (resultSet.next()) {
          return resultSet.getInt(1);
        } else {
          throw new CouldntSaveDataException("Empty result set while inserting type member.");
        }
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Create an association of an operand tree node and the nth member of a given base type in the
   * database.
   *
   * @param connection The connection to the database.
   * @param treeNodeId The tree node that is associated with a type.
   * @param baseTypeId the id of the base type that should be associated with the tree node.
   * @param position The zero-based index position of the operand within its instruction.
   * @param offset An offset to a member contained in the corresponding base type (in bits).
   * @param address The address of the instruction that is annotated with this type substitution.
   * @throws CouldntSaveDataException Thrown if the type substitution couldn't be written to the
   *         database.
   */
  public static void createTypeSubstitution(final Connection connection,
      final int treeNodeId,
      final int baseTypeId,
      final List<Integer> memberPath,
      final int position,
      final int offset,
      final IAddress address,
      final INaviModule module) throws CouldntSaveDataException {
    try {
      final String query = String.format(
          "INSERT INTO %s (module_id, address, \"position\", \"offset\", expression_id, path, "
          + "base_type_id) VALUES (?, ?, ?, ?, ?, ?, ?)", CTableNames.EXPRESSION_TYPES_TABLE);
      final PreparedStatement statement = connection.prepareStatement(query);
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setLong(2, address.toLong());
        statement.setInt(3, position);
        statement.setInt(4, offset);
        statement.setInt(5, treeNodeId);
        statement.setArray(6, connection.createArrayOf("int4", memberPath.toArray()));
        statement.setInt(7, baseTypeId);
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Deletes a member from the database.
   *
   * @param connection The connection to the database.
   * @param member The member to delete.
   * @param module The module that contains the member.
   * @throws CouldntDeleteException Thrown if the member couldn't be deleted from the database.
   */
  public static void deleteMember(final Connection connection, final TypeMember member,
      final INaviModule module) throws CouldntDeleteException {
    try {
      final CallableStatement statement = connection.prepareCall("{ call delete_type(?, ?) }");
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setInt(2, member.getId());
        statement.execute();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * Delete a base type from the database.
   *
   * @param connection The connection to the database.
   * @param baseType The type to be deleted from the database.
   * @param module The module which contains the base type.
   * @throws CouldntDeleteException Thrown if the type couldn't be deleted from the database.
   */
  public static void deleteType(final Connection connection, final BaseType baseType,
      final INaviModule module) throws CouldntDeleteException {
    try {
      final PreparedStatement statement = connection.prepareStatement(
          "DELETE FROM " + CTableNames.BASE_TYPES_TABLE + " WHERE module_id = ? AND id = ?");
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setInt(2, baseType.getId());
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * This function deletes a {@link TypeInstance} from the database.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param moduleId The id of the {@link INaviModule} of the {@link TypeInstance}.
   * @param typeInstanceId The id of the {@link TypeInstance}.
   *
   * @throws CouldntDeleteException if the {@link TypeInstance} could not be deleted from the
   *         database.
   */
  public static void deleteTypeInstance(final PostgreSQLProvider provider, final int moduleId,
      final int typeInstanceId) throws CouldntDeleteException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be larger then zero");
    Preconditions.checkArgument(typeInstanceId >= 0,
        "Error: type instance id must be larger or equal to zero");

    final String function = " { call delete_type_instance(?, ?) } ";
    try {

      final CallableStatement deleteCommentStatement =
          provider.getConnection().getConnection().prepareCall(function);

      try {
        deleteCommentStatement.setInt(1, moduleId);
        deleteCommentStatement.setInt(2, typeInstanceId);
        deleteCommentStatement.execute();

      } finally {
        deleteCommentStatement.close();
      }

    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * This function deletes a type instance comment associated with the given type instance from the
   * database.
   *
   * @param provider The provider used to access the database.
   * @param moduleId The module id to which the type instance is associated.
   * @param instanceId The id of the type instance where the comment is associated to.
   * @param commentId The id of the comment which is to be deleted.
   * @param userId The id of the currently active user.
   *
   * @throws CouldntDeleteException if the comment could not be deleted from the database.
   */
  public static void deleteTypeInstanceComment(final SQLProvider provider, final int moduleId,
      final int instanceId, final Integer commentId, final Integer userId)
      throws CouldntDeleteException {

    Preconditions.checkArgument(moduleId > 0, "Error: module id must be greater then zero");
    Preconditions.checkArgument(instanceId >= 0,
        "Error: instance id must be greater or equal to zero");
    Preconditions.checkNotNull(commentId, "Error: comment text argument can not be null");
    Preconditions.checkNotNull(userId, "Error: user id argument can not be null");

    final String function = " { ? = call delete_type_instance_comment(?, ?, ?, ?) } ";

    try {

      final CallableStatement deleteCommentStatement =
          provider.getConnection().getConnection().prepareCall(function);

      try {
        deleteCommentStatement.registerOutParameter(1, Types.INTEGER);
        deleteCommentStatement.setInt(2, moduleId);
        deleteCommentStatement.setInt(3, instanceId);
        deleteCommentStatement.setInt(4, commentId);
        deleteCommentStatement.setInt(5, userId);

        deleteCommentStatement.execute();

        deleteCommentStatement.getInt(1);
        if (deleteCommentStatement.wasNull()) {
          throw new IllegalArgumentException(
              "Error: The comment id returned from the database was null.");
        }

      } finally {
        deleteCommentStatement.close();
      }

    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * This function deletes a {@link TypeInstanceReference} from the database.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param moduleId The {@link INaviModule} id of the {@link TypeInstanceReference}.
   * @param address The address of the {@link INaviInstruction} the {@link TypeInstanceReference} is
   *        associated to.
   * @param position The position of the {@link INaviOperandTree} in the {@link INaviInstruction}
   *        the {@link TypeInstanceReference} is associated to.
   * @param expressionId The id of the {@link INaviOperandTreeNode} the
   *        {@link TypeInstanceReference} is associated to.
   *
   * @throws CouldntDeleteException if the {@link TypeInstanceReference} could not deleted from the
   *         database.
   */
  public static void deleteTypeInstanceReference(final PostgreSQLProvider provider,
      final int moduleId, final BigInteger address, final int position, final int expressionId)
      throws CouldntDeleteException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be larger then zero");
    Preconditions.checkNotNull(address, "Error: address argument can not be null");
    Preconditions.checkArgument(position >= 0, "Error: position must be larger or equal to zero");
    Preconditions.checkArgument(expressionId >= 0,
        "Error: expression id must be larger or equal to zero");

    final String function = " { call delete_expression_type_instance(?, ?, ?, ?) } ";

    try {

      final CallableStatement deleteCommentStatement =
          provider.getConnection().getConnection().prepareCall(function);

      try {
        deleteCommentStatement.setInt(1, moduleId);
        deleteCommentStatement.setObject(2, address, Types.OTHER);
        deleteCommentStatement.setInt(3, position);
        deleteCommentStatement.setInt(4, expressionId);
        deleteCommentStatement.execute();

      } finally {
        deleteCommentStatement.close();
      }

    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * Deletes the given type substitution from the database.
   *
   * @param connection The connection to the database.
   * @param module The module that contains the type substitution.
   * @param typeSubstitution The type substitution to delete.
   * @throws CouldntDeleteException Thrown if the type substitution couldn't be deleted.
   */
  public static void deleteTypeSubstitution(final Connection connection, final INaviModule module,
      final TypeSubstitution typeSubstitution) throws CouldntDeleteException {
    try {
      final PreparedStatement statement = connection.prepareStatement("DELETE FROM "
          + CTableNames.EXPRESSION_TYPES_TABLE
          + " WHERE module_id = ? AND address = ? AND \"position\" = ? AND expression_id = ?");
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setLong(2, typeSubstitution.getAddress().toLong());
        statement.setInt(3, typeSubstitution.getPosition());
        statement.setInt(4, typeSubstitution.getExpressionId());
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntDeleteException(exception);
    }
  }

  /**
   * This function edits a type instance comment.
   *
   * @param provider The provider used to access the database.
   * @param moduleId The module id to which the type instance is associated to.
   * @param commentId The id of the comment associated to the type instance which gets edited.
   * @param userId The id of the currently active user.
   * @param commentText The new text of the comment.
   *
   * @throws CouldntSaveDataException if the comment could not be edited in the database.
   */
  public static void editTypeInstanceComment(final SQLProvider provider, final int moduleId,
      final Integer commentId, final Integer userId, final String commentText)
      throws CouldntSaveDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be greater then zero");
    Preconditions.checkNotNull(commentId, "Error: commentId argument can not be null");
    Preconditions.checkNotNull(userId, "Error: userId argument can not be null");
    Preconditions.checkNotNull(commentText, "Error: commentText argument can not be null");

    PostgreSQLCommentFunctions.editComment(provider, commentId, userId, commentText);
  }

  /**
   * Loads a single {@link RawBaseType base type} from the database.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param module The {@link INaviModule} to which this {@link RawBaseType base type} is associated
   *        to.
   * @param baseTypeId The id of the {@link RawBaseType base type} to load from the database.
   *
   * @return The {@link RawBaseType base type} from the database which matches the given arguments.
   * @throws CouldntLoadDataException
   */
  public static RawBaseType loadRawBaseType(final SQLProvider provider, final INaviModule module,
      final Integer baseTypeId) throws CouldntLoadDataException {
    Preconditions.checkNotNull(provider, "Error: provider argument can not be null.");
    Preconditions.checkNotNull(module, "Error: module argument can not be null.");
    Preconditions.checkNotNull(baseTypeId, "Error: baseTypeId argument can not be null.");

    final String query = " SELECT * FROM load_type(?, ?) ";
    try {
      final PreparedStatement statement = provider.getConnection().getConnection()
          .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      statement.setInt(1, module.getConfiguration().getId());
      statement.setInt(2, baseTypeId);
      final ResultSet resultSet = statement.executeQuery();
      try {
        while (resultSet.next()) {
          if (resultSet.first()) {
            Integer pointer = resultSet.getInt("pointer");
            if (resultSet.wasNull()) {
              pointer = null;
            }
            return new RawBaseType(resultSet.getInt("id"),
                resultSet.getString("name"),
                resultSet.getInt("size"),
                pointer,
                resultSet.getBoolean("signed"),
                BaseTypeCategory.fromString(resultSet.getString("category")));
          }
        }
      } finally {
        resultSet.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }

    throw new CouldntLoadDataException("Error: could not load single base type from the database.");
  }

  /**
   * Loads a single {@link RawTypeInstance type instance} from the database.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param module The {@link INaviModule} the {@link RawTypeInstance type instance} is associated
   *        to.
   * @param typeInstanceId The id of the {@link RawTypeInstance type instance} to load from the
   *        database.
   *
   * @return The {@link RawTypeInstance type instance} from the database which matches the given
   *         arguments.
   * @throws CouldntLoadDataException if the {@link RawTypeInstance type instance} could not be
   *         loaded from the database.
   */
  public static RawTypeInstance loadRawTypeInstance(final SQLProvider provider,
      final INaviModule module, final Integer typeInstanceId) throws CouldntLoadDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");
    Preconditions.checkNotNull(typeInstanceId, "Error: typeInstanceId argument can not be null");

    final String query = " SELECT * FROM load_type_instance(?, ?) ";

    try {
      final PreparedStatement statement = provider.getConnection().getConnection()
          .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

      statement.setInt(1, module.getConfiguration().getId());
      statement.setInt(2, typeInstanceId);
      final ResultSet resultSet = statement.executeQuery();
      try {
        while (resultSet.next()) {
          if (resultSet.first()) {
            final int moduleId = resultSet.getInt("module_id");
            final int id = resultSet.getInt("id");
            final String name = resultSet.getString("name");
            final int commentId = resultSet.getInt("comment_id");
            final int typeId = resultSet.getInt("type_id");
            final int sectionId = resultSet.getInt("section_id");
            final long sectionOffset = resultSet.getLong("section_offset");
            return new RawTypeInstance(moduleId,
                id,
                name,
                commentId,
                typeId,
                sectionId,
                sectionOffset);
          }
        }
      } finally {
        resultSet.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }

    throw new CouldntLoadDataException(
        "Error: could not load singe type instance from the database.");
  }

  /**
   * Loads a single {@link RawTypeInstanceReference cross reference} from the database.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param module The {@link INaviModule} the {@link RawTypeInstanceReference cross reference} is
   *        associated to.
   * @param typeInstanceId The id of the {@link RawTypeInstance type instance} this
   *        {@link RawTypeInstanceReference cross reference} references.
   * @param address The {@link INaviInstruction instruction} address where this
   *        {@link RawTypeInstanceReference cross reference} is associated to.
   * @param position The {@link INaviOperandTree operand tree} position to which this
   *        {@link RawTypeInstanceReference type reference} is associated to.
   * @param expressionId The {@link INaviOperandTreeNode operand node} where this
   *        {@link RawTypeInstanceReference type reference} is associated to.
   *
   * @return The {@link RawTypeInstanceReference type reference} from the database which matches the
   *         given arguments.
   * @throws CouldntLoadDataException
   */
  public static RawTypeInstanceReference loadRawTypeInstanceReference(final SQLProvider provider,
      final INaviModule module,
      final Integer typeInstanceId,
      final BigInteger address,
      final Integer position,
      final Integer expressionId) throws CouldntLoadDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");
    Preconditions.checkNotNull(typeInstanceId, "Error: typeInstanceId argument can not be null");
    Preconditions.checkNotNull(address, "Error: address argument can not be null");
    Preconditions.checkNotNull(position, "Error: position argument can not be null");
    Preconditions.checkNotNull(expressionId, "Error: expressionId argument can not be null");

    final String query = " SELECT * FROM load_expression_type_instance(?, ?, ?, ?, ?) ";

    try {
      final PreparedStatement statement = provider.getConnection().getConnection()
          .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

      statement.setInt(1, module.getConfiguration().getId());
      statement.setInt(2, typeInstanceId);
      statement.setObject(3, address, Types.BIGINT);
      statement.setInt(4, position);
      statement.setInt(5, expressionId);
      final ResultSet resultSet = statement.executeQuery();

      try {
        while (resultSet.next()) {
          if (resultSet.isFirst()) {
            final int viewId = resultSet.getInt("view_id");
            final int moduleId = resultSet.getInt("module_id");
            return new RawTypeInstanceReference(moduleId,
                viewId,
                new CAddress(address),
                position,
                expressionId,
                typeInstanceId);
          }
        }

      } finally {
        resultSet.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    throw new CouldntLoadDataException(
        "Error: could not load single cross reference from the database.");
  }

  public static List<RawTypeInstanceReference> loadRawTypeInstanceReferences(
      final Connection connection, final INaviModule module) throws CouldntLoadDataException {

    final ArrayList<RawTypeInstanceReference> rawReferences = Lists.newArrayList();

    final String query = " SELECT * FROM load_expression_type_instances(?) ";

    try {
      final PreparedStatement statement = connection.prepareStatement(query);
      statement.setInt(1, module.getConfiguration().getId());
      try {
        final ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
          final int viewId = resultSet.getInt("view_id");
          final int moduleId = resultSet.getInt("module_id");
          final IAddress address = PostgreSQLHelpers.loadAddress(resultSet, "address");
          final int position = resultSet.getInt("position");
          final int expressionId = resultSet.getInt("expression_id");
          final int typeInstanceId = resultSet.getInt("type_instance_id");

          rawReferences.add(new RawTypeInstanceReference(moduleId,
              viewId,
              address,
              position,
              expressionId,
              typeInstanceId));
        }

      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    return rawReferences;
  }

  /**
   * Loads all type instances for the given module from the database.
   *
   * @param connection The connection to the database.
   * @param module The module for which to load all type instances.
   * @return The list of type instances for the given module.
   * @throws CouldntLoadDataException Thrown if the type instances could not be loaded from the
   *         database.
   */
  public static List<RawTypeInstance> loadRawTypeInstances(final Connection connection,
      final INaviModule module) throws CouldntLoadDataException {
    final List<RawTypeInstance> instances = new ArrayList<RawTypeInstance>();
    try {

      final String query = "SELECT * FROM load_type_instances(?)";

      final PreparedStatement statement = connection.prepareStatement(query);
      statement.setInt(1, module.getConfiguration().getId());
      try {
        final ResultSet result = statement.executeQuery();
        while (result.next()) {
          final int id = result.getInt("id");
          final String name = result.getString("name");
          Integer commentId = result.getInt("comment_id");
          if (result.wasNull()) {
            commentId = null;
          }
          final int typeId = result.getInt("type_id");
          final int sectionId = result.getInt("section_id");
          final long sectionOffset = result.getLong("section_offset");
          instances.add(new RawTypeInstance(module.getConfiguration().getId(),
              id,
              name,
              commentId,
              typeId,
              sectionId,
              sectionOffset));
        }
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    return instances;
  }

  /**
   * Loads a single {@link RawTypeMember type member} from the database
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param module The {@link INaviModule} to which this {@link RawTypeMember type member} is
   *        associated to.
   * @param typeId The id of the {@link RawTypeMember type member} to load from the database.
   *
   * @return The {@link RawTypeMember type member} from the database which matches the given
   *         arguments.
   * @throws CouldntLoadDataException if the {@link RawTypeMember type member} could not be loaded
   *         from the database.
   */
  public static RawTypeMember loadRawTypeMember(final SQLProvider provider,
      final INaviModule module, final Integer typeId) throws CouldntLoadDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");
    Preconditions.checkNotNull(typeId, "Error: typeId argument can not be null");

    final String query = " SELECT * FROM load_type_member(?, ?) ";
    try {
      final PreparedStatement statement = provider.getConnection().getConnection()
          .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      statement.setInt(1, module.getConfiguration().getId());
      statement.setInt(2, typeId);
      final ResultSet resultSet = statement.executeQuery();

      try {
        while (resultSet.next()) {
          if (resultSet.first()) {
            final int id = resultSet.getInt("id");
            final String name = resultSet.getString("name");
            final int baseTypeId = resultSet.getInt("base_type");
            final Integer parentId = resultSet.getInt("parent_id");
            Integer offset = resultSet.getInt("offset");
            if (resultSet.wasNull()) {
              offset = null;
            }
            Integer argument = resultSet.getInt("argument");
            if (resultSet.wasNull()) {
              argument = null;
            }
            Integer numberOfElements = resultSet.getInt("number_of_elements");
            if (resultSet.wasNull()) {
              numberOfElements = null;
            }
            return new RawTypeMember(id,
                name,
                baseTypeId,
                parentId,
                offset,
                argument,
                numberOfElements);
          }
        }
      } finally {
        resultSet.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }

    throw new CouldntLoadDataException(
        "Error: could not load single type member from the database.");
  }

  /**
   * Loads the raw type members for the given module from the database.
   *
   * @param connection The connection to the database.
   * @param module The module where the type members belong to.
   * @return The list of type members.
   * @throws CouldntLoadDataException Thrown if the type members couldn't be loaded.
   */
  public static List<RawTypeMember> loadRawTypeMembers(final Connection connection,
      final INaviModule module) throws CouldntLoadDataException {

    Preconditions.checkNotNull(connection, "Error: connection argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");
    final List<RawTypeMember> rawMembers = new ArrayList<RawTypeMember>();

    final String query = " SELECT * FROM load_type_members(?) ";
    try {
      final PreparedStatement statement = connection.prepareStatement(query);
      statement.setInt(1, module.getConfiguration().getId());
      final ResultSet results = statement.executeQuery();
      try {
        while (results.next()) {
          final Integer parentId = results.getInt("parent_id");
          if (results.wasNull()) {
            continue;
          }
          Integer argument = results.getInt("argument");
          if (results.wasNull()) {
            argument = null;
          }
          Integer offset = results.getInt("offset");
          if (results.wasNull()) {
            offset = null;
          }
          Integer numberOfElements = results.getInt("number_of_elements");
          if (results.wasNull()) {
            numberOfElements = null;
          }
          rawMembers.add(new RawTypeMember(results.getInt("id"),
              results.getString("name"),
              results.getInt("base_type"),
              parentId,
              offset,
              argument,
              numberOfElements));
        }
      } finally {
        results.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    return rawMembers;
  }

  /**
   * Loads all {@link RawBaseType} for the given module from the database.
   *
   * @param connection The {@link Connection} to the database.
   * @param module The {@link INaviModule} whose types should be loaded from the database.
   * @return The list of all loaded base types.
   * @throws CouldntLoadDataException Thrown if the raw types coudln't be loaded from the database.
   */
  public static List<RawBaseType> loadRawTypes(final Connection connection,
      final INaviModule module) throws CouldntLoadDataException {
    final String query = " SELECT * FROM load_types(?) ";
    final List<RawBaseType> rawTypes = new ArrayList<RawBaseType>();

    try {
      final PreparedStatement statement = connection.prepareStatement(query);
      statement.setInt(1, module.getConfiguration().getId());
      final ResultSet results = statement.executeQuery();
      try {
        while (results.next()) {
          Integer pointer = results.getInt("pointer");
          if (results.wasNull()) {
            pointer = null;
          }
          rawTypes.add(new RawBaseType(results.getInt("id"),
              results.getString("name"),
              results.getInt("size"),
              pointer,
              results.getBoolean("signed"),
              BaseTypeCategory.fromString(results.getString("category"))));
        }
      } finally {
        results.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    return rawTypes;
  }

  /**
   * Loads a single type substitution from the database.
   *
   * @param provider The {@link SQLProvider} used to access the database.
   * @param module The {@link INaviModule} this {@link RawTypeSubstitution} is associated to.
   * @param address of the {@link INaviInstruction instruction} where the
   *        {@link RawTypeSubstitution} is associated to.
   * @param position of the {@link INaviOperandTree operand tree} where the
   *        {@link RawTypeSubstitution} is associated to.
   * @param expressionId of the {@link INaviOperandTreeNode operand tree node} in the
   *        {@link INaviOperandTree operand tree} where the {@link RawTypeSubstitution type
   *        substitution} is associated to.
   *
   * @return The {@link RawTypeSubstitution type substitution} from the database which matches the
   *         given arguments.
   * @throws CouldntLoadDataException if the single {@link RawTypeSubstitution type substitution}
   *         could not be loaded from the database.
   */
  public static RawTypeSubstitution loadRawTypeSubstitution(final SQLProvider provider,
      final INaviModule module, final BigInteger address, final Integer position,
      final Integer expressionId) throws CouldntLoadDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");
    Preconditions.checkNotNull(address, "Error: address argument can not be null");
    Preconditions.checkNotNull(position, "Error: position argument can not be null");
    Preconditions.checkNotNull(expressionId, "Error: expressionId argument can not be null");

    final String query = " SELECT * FROM load_type_substitution(?, ?, ?, ?) ";
    try {
      final PreparedStatement statement = provider.getConnection().getConnection()
          .prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
      statement.setInt(1, module.getConfiguration().getId());
      statement.setObject(2, address, Types.BIGINT);
      statement.setInt(3, position);
      statement.setInt(4, expressionId);
      final ResultSet resultSet = statement.executeQuery();
      try {
        while (resultSet.next()) {
          if (resultSet.first()) {
            final int baseTypeId = resultSet.getInt("base_type_id");
            final Array arr = resultSet.getArray("path");
            final Integer[] path =
                resultSet.wasNull() ? new Integer[0] : (Integer[]) arr.getArray();
            Integer offset = resultSet.getInt("offset");
            if (resultSet.wasNull()) {
              offset = null;
            }
            return new RawTypeSubstitution(new CAddress(address),
                position,
                expressionId,
                baseTypeId,
                path,
                offset);
          }
        }
      } finally {
        resultSet.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }

    throw new CouldntLoadDataException(
        "Error: could not load single type substitution from the database.");
  }

  /**
   * Loads all {@link RawTypeSubstitution} for the given module from the database.
   *
   * @param connection The {@link Connection} to access the database with.
   * @param module The {@link INaviModule} to load the {@link RawTypeSubstitution} for.
   *
   * @return The {@link List} of {@link RawTypeSubstitution} for the given {@link INaviModule}.
   *
   * @throws CouldntLoadDataException if the {@link RawTypeSubstitution} could not be loaded from
   *         the database.
   */
  public static List<RawTypeSubstitution> loadRawTypeSubstitutions(final Connection connection,
      final INaviModule module) throws CouldntLoadDataException {

    Preconditions.checkNotNull(connection, "Error: connection argument can not be null");
    Preconditions.checkNotNull(module, "Error: module argument can not be null");

    final String query = " SELECT * FROM load_type_substitutions(?) ";
    final List<RawTypeSubstitution> rawSubstitutions = new ArrayList<RawTypeSubstitution>();

    try {
      final PreparedStatement statement = connection.prepareStatement(query);
      statement.setInt(1, module.getConfiguration().getId());
      final ResultSet results = statement.executeQuery();
      try {
        while (results.next()) {
          final long address = results.getLong("address");
          final int position = results.getInt("position");
          final int expressionId = results.getInt("expression_id");
          final int baseTypeId = results.getInt("base_type_id");
          final Array arr = results.getArray("path");
          Integer[] path = (Integer[]) arr.getArray();
          if (results.wasNull()) {
            path = new Integer[0];
          }
          Integer offset = results.getInt("offset");
          if (results.wasNull()) {
            offset = null;
          }
          rawSubstitutions.add(new RawTypeSubstitution(new CAddress(address),
              position,
              expressionId,
              baseTypeId,
              path,
              offset));
        }
      } finally {
        results.close();
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntLoadDataException(exception);
    }
    return rawSubstitutions;
  }

  /**
   * This function sets the name of a {@link TypeInstance} in the database to the given name.
   *
   * @param provider The {@link SQLProvider} to access the database with.
   * @param moduleId The id of the {@link INaviModule} the {@link TypeInstance} is associated to.
   * @param id The id of the {@link TypeInstance}.
   * @param name The new name of the {@link TypeInstance}.
   *
   * @throws CouldntSaveDataException if the changes to the {@link TypeInstance} name could not be
   *         saved to the database.
   */
  public static void setTypeInstanceName(final PostgreSQLProvider provider, final int moduleId,
      final int id, final String name) throws CouldntSaveDataException {

    Preconditions.checkNotNull(provider, "Error: provider argument can not be null");
    Preconditions.checkArgument(moduleId > 0, "Error: module id must be larger then zero");
    Preconditions.checkArgument(id >= 0, "Error: type instance id must be larger or equal to zero");
    Preconditions.checkNotNull(name, "Error: name argument can not be null");

    final String function = " { call set_type_instance_name(?, ?, ?) } ";

    try {

      final CallableStatement setTypeInstanceNameStatement =
          provider.getConnection().getConnection().prepareCall(function);

      try {
        setTypeInstanceNameStatement.setInt(1, moduleId);
        setTypeInstanceNameStatement.setInt(2, id);
        setTypeInstanceNameStatement.setString(3, name);
        setTypeInstanceNameStatement.execute();

      } finally {
        setTypeInstanceNameStatement.close();
      }

    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Increments the offsets of all members which have an offset > startOffset.
   *
   * @param connection The connection to the database.
   * @param members The ids of the members whose updates should be updated.
   * @param delta The value that is added to all member offsets.
   * @param implicitDelta The delta that is added to the implicitly update member offsets.
   * @param implicitlyUpdatedMembers
   * @param module The module that contains the members.
   * @throws CouldntSaveDataException Thrown if the member offsets could not be updated.
   */
  public static void updateMemberOffsets(final Connection connection,
      final List<Integer> members,
      final int delta,
      final List<Integer> implicitlyUpdatedMembers,
      final int implicitDelta,
      final INaviModule module) throws CouldntSaveDataException {
    try {
      final CallableStatement statement =
          connection.prepareCall("{ call update_member_offsets(?, ?, ?, ?, ?) }");
      try {
        statement.setInt(1, module.getConfiguration().getId());
        statement.setArray(2, connection.createArrayOf("int4", members.toArray()));
        statement.setInt(3, delta);
        statement.setArray(4, connection.createArrayOf("int4", implicitlyUpdatedMembers.toArray()));
        statement.setInt(5, implicitDelta);
        statement.execute();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Updates the record of the given base type in the database.
   *
   * @param connection The connection to the database.
   * @param baseType The type whose database representation should be updated.
   * @param name The new name of the base type.
   * @param size The new size of the base type.
   * @param isSigned The new signedness of the base type.
   * @param module The module which contains the base type.
   * @throws CouldntSaveDataException Thrown if the database record couldn't be updated.
   */
  public static void updateType(final Connection connection,
      final BaseType baseType,
      final String name,
      final int size,
      final boolean isSigned,
      final INaviModule module) throws CouldntSaveDataException {
    try {
      final PreparedStatement statement = connection.prepareStatement("UPDATE "
          + CTableNames.BASE_TYPES_TABLE
          + " SET name = ?, size = ?, signed = ? WHERE module_id = ? AND id = ?");
      try {
        statement.setString(1, name);
        statement.setInt(2, size);
        statement.setBoolean(3, isSigned);
        statement.setInt(4, module.getConfiguration().getId());
        statement.setInt(5, baseType.getId());
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Updates the member belonging to the given base type in the database.
   *
   * @param connection The connection to the database.
   * @param member The member to update.
   * @param newName The new name of the member.
   * @param newBaseType The new base type of the member.
   * @param newNumberOfElements The new number of elements the member has.
   * @param newArgumentIndex The new argument index of the member.
   * @param module The module which contains the type member.
   * @throws CouldntSaveDataException Thrown if the member couldn't be updated in the database.
   */
  public static void updateTypeMember(final Connection connection,
      final TypeMember member,
      final String newName,
      final BaseType newBaseType,
      final Optional<Integer> offset,
      final Optional<Integer> newNumberOfElements,
      final Optional<Integer> newArgumentIndex,
      final INaviModule module) throws CouldntSaveDataException {
    Preconditions.checkNotNull(member, "Error: member argument can not be null.");
    Preconditions.checkNotNull(newName, "Error: new name argument can not be null");
    Preconditions.checkNotNull(newBaseType, "Error: new base type argument can not be null.");
    Preconditions.checkNotNull(offset, "Error: offset argument can not be null");
    Preconditions.checkNotNull(newNumberOfElements,
        "Error: new number of elements argument can not be null.");
    Preconditions.checkNotNull(newArgumentIndex,
        "Error: new argument index argument can not be null.");
    try {
      final PreparedStatement statement = connection.prepareStatement("UPDATE "
          + CTableNames.TYPE_MEMBERS_TABLE
          + " SET name = ?, base_type = ?, parent_id = ?, \"offset\" = ?, "
          + "number_of_elements = ?, argument = ? WHERE module_id = ? AND id = ?");
      try {
        statement.setString(1, newName);
        statement.setInt(2, newBaseType.getId());
        statement.setInt(3, member.getParentType().getId());
        if (offset.isPresent()) {
          statement.setInt(4, offset.get());
        } else {
          statement.setNull(4, Types.INTEGER);
        }
        if (newNumberOfElements.isPresent()) {
          statement.setInt(5, newNumberOfElements.get());
        } else {
          statement.setNull(5, Types.INTEGER);
        }
        if (newArgumentIndex.isPresent()) {
          statement.setInt(6, newArgumentIndex.get());
        } else {
          statement.setNull(6, Types.INTEGER);
        }
        statement.setInt(7, module.getConfiguration().getId());
        statement.setInt(8, member.getId());
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }

  /**
   * Updates the given type substitution in the database.
   *
   * @param connection The connection to the database.
   * @param substitution The type substitution to update.
   * @param baseType The new base type for the type substitution.
   * @param position The zero-based index position of the operand within its instruction.
   * @param offset The new offset for the type substitution.
   * @param module The module that contains the type substitution.
   * @throws CouldntSaveDataException Thrown if the type substitution could not be updated in the
   *         database.
   */
  public static void updateTypeSubstitution(final Connection connection,
      final TypeSubstitution substitution,
      final BaseType baseType,
      final List<Integer> memberPath,
      final int position,
      final int offset,
      final INaviModule module) throws CouldntSaveDataException {
    try {
      final PreparedStatement statement = connection.prepareStatement("UPDATE "
          + CTableNames.EXPRESSION_TYPES_TABLE
          + " SET base_type_id = ?, \"position\" = ?, \"offset\" = ?, path = ? "
          + "WHERE module_id = ? AND expression_id = ? AND address = ?");
      try {
        statement.setInt(1, baseType.getId());
        statement.setInt(2, position);
        statement.setInt(3, offset);
        statement.setArray(4, connection.createArrayOf("int4", memberPath.toArray()));
        statement.setInt(5, module.getConfiguration().getId());
        statement.setInt(6, substitution.getExpressionId());
        statement.setLong(7, substitution.getAddress().toLong());
        statement.executeUpdate();
      } finally {
        statement.close();
      }
    } catch (final SQLException exception) {
      throw new CouldntSaveDataException(exception);
    }
  }
}
