org.wso2.siddhi.extension.table.rdbms.RDBMSEventTable.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.siddhi.extension.table.rdbms.RDBMSEventTable.java

Source

/*
*  Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you 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 org.wso2.siddhi.extension.table.rdbms;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.siddhi.annotation.Example;
import org.wso2.siddhi.annotation.Extension;
import org.wso2.siddhi.annotation.Parameter;
import org.wso2.siddhi.annotation.util.DataType;
import org.wso2.siddhi.core.exception.CannotLoadConfigurationException;
import org.wso2.siddhi.core.table.record.AbstractRecordTable;
import org.wso2.siddhi.core.table.record.ConditionBuilder;
import org.wso2.siddhi.core.table.record.RecordIterator;
import org.wso2.siddhi.core.util.SiddhiConstants;
import org.wso2.siddhi.core.util.collection.operator.CompiledCondition;
import org.wso2.siddhi.core.util.config.ConfigReader;
import org.wso2.siddhi.extension.table.rdbms.config.RDBMSQueryConfigurationEntry;
import org.wso2.siddhi.extension.table.rdbms.config.RDBMSTypeMapping;
import org.wso2.siddhi.extension.table.rdbms.exception.RDBMSTableException;
import org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableUtils;
import org.wso2.siddhi.query.api.annotation.Annotation;
import org.wso2.siddhi.query.api.annotation.Element;
import org.wso2.siddhi.query.api.definition.Attribute;
import org.wso2.siddhi.query.api.definition.TableDefinition;
import org.wso2.siddhi.query.api.util.AnnotationHelper;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import static org.wso2.siddhi.core.util.SiddhiConstants.ANNOTATION_STORE;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_FIELD_LENGTHS;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_JNDI_RESOURCE;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_PASSWORD;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_POOL_PROPERTIES;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_TABLE_NAME;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_URL;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.ANNOTATION_ELEMENT_USERNAME;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.CLOSE_PARENTHESIS;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.EQUALS;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.OPEN_PARENTHESIS;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_COLUMNS;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_COLUMNS_VALUES;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_CONDITION;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_INDEX;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_Q;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.PLACEHOLDER_TABLE_NAME;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.QUESTION_MARK;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.SEPARATOR;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.SQL_PRIMARY_KEY_DEF;
import static org.wso2.siddhi.extension.table.rdbms.util.RDBMSTableConstants.WHITESPACE;

/**
 * Class representing the RDBMS Event Table implementation.
 */
@Extension(name = "rdbms", namespace = "store", description = "Using this extension the data source or the connection instructions can be "
        + "assigned to the event table.", parameters = {
                @Parameter(name = "jdbc.url", description = "The JDBC URL for the RDBMS data store.", type = {
                        DataType.STRING }),
                @Parameter(name = "username", description = "The username for accessing the data store.", type = {
                        DataType.STRING }),
                @Parameter(name = "password", description = "The password for accessing the data store.", type = {
                        DataType.STRING }),
                @Parameter(name = "pool.properties", description = "Any pool properties for the DB connection, specified as key-value pairs.", type = {
                        DataType.STRING }),
                @Parameter(name = "jndi.resource", description = "Optional. The name of the JNDI resource (if any). If found, the connection "
                        + "parameters are not taken into account, and the connection will be attempted through "
                        + "JNDI lookup instead.", type = { DataType.STRING }),
                @Parameter(name = "table.name", description = "Optional. The name of the table in the store this Event Table should be "
                        + "persisted as. If not specified, the table name will be the same as the Siddhi table.", type = {
                                DataType.STRING }),
                @Parameter(name = "field.length", description = "Optional. The length of any String fields the table definition contains. If not "
                        + "specified, the vendor-specific DB default will be chosen.", type = {
                                DataType.STRING }), }, examples = {
                                        @Example(syntax = "@Store(type=\"rdbms\", jdbc.url=\"jdbc:mysql://localhost:3306/das\", "
                                                + "username=\"root\", password=\"root\",field.length=\"symbol:100\")\n"
                                                + "@PrimaryKey(\"symbol\")" + "@Index(\"volume\")"
                                                + "define table StockTable (symbol string, price float, volume long);", description = "The above example will create an Event Table named 'StockTable' on the DB if "
                                                        + "it doesn't already exist (with 3 fields 'symbol', 'price', and 'volume' with types"
                                                        + "string, float and long respectively). The connection parameters will be as"
                                                        + " specified in the '@Store' annotation. The 'symbol' field will be declared a unique "
                                                        + "field, and a DB index will be created for the 'symbol' field.") })
public class RDBMSEventTable extends AbstractRecordTable {

    private static final Log log = LogFactory.getLog(RDBMSEventTable.class);
    private RDBMSQueryConfigurationEntry queryConfigurationEntry;
    private DataSource dataSource;
    private String tableName;
    private List<Attribute> attributes;

    @Override
    protected void init(TableDefinition tableDefinition, ConfigReader configReader) {
        this.attributes = tableDefinition.getAttributeList();
        Annotation storeAnnotation = AnnotationHelper.getAnnotation(ANNOTATION_STORE,
                tableDefinition.getAnnotations());
        Annotation primaryKeys = AnnotationHelper.getAnnotation(SiddhiConstants.ANNOTATION_PRIMARY_KEY,
                tableDefinition.getAnnotations());
        Annotation indices = AnnotationHelper.getAnnotation(SiddhiConstants.ANNOTATION_INDEX,
                tableDefinition.getAnnotations());
        RDBMSTableUtils.validateAnnotation(primaryKeys);
        RDBMSTableUtils.validateAnnotation(indices);
        String jndiResourceName = storeAnnotation.getElement(ANNOTATION_ELEMENT_JNDI_RESOURCE);
        if (!RDBMSTableUtils.isEmpty(jndiResourceName)) {
            try {
                this.lookupDatasource(jndiResourceName);
            } catch (NamingException e) {
                throw new RDBMSTableException("Failed to lookup datasource with provided JNDI resource name '"
                        + jndiResourceName + "': " + e.getMessage(), e);
            }
        } else {
            this.initializeDatasource(storeAnnotation);
        }
        String tableName = storeAnnotation.getElement(ANNOTATION_ELEMENT_TABLE_NAME);
        this.tableName = RDBMSTableUtils.isEmpty(tableName) ? tableDefinition.getId() : tableName;
        try {
            if (this.queryConfigurationEntry == null) {
                this.queryConfigurationEntry = RDBMSTableUtils
                        .lookupCurrentQueryConfigurationEntry(this.dataSource);
            }
        } catch (CannotLoadConfigurationException e) {
            throw new RDBMSTableException("Failed to initialize DB Configuration entry for table '" + this.tableName
                    + "': " + e.getMessage(), e);
        }
        if (!this.tableExists()) {
            this.createTable(storeAnnotation, primaryKeys, indices);
        }
    }

    @Override
    protected void add(List<Object[]> records) {
        String sql = this.composeInsertQuery();
        try {
            this.batchExecuteQueriesWithRecords(sql, records, false);
        } catch (SQLException e) {
            throw new RDBMSTableException(
                    "Error in adding events to '" + this.tableName + "' store: " + e.getMessage(), e);
        }
    }

    @Override
    protected RecordIterator<Object[]> find(Map<String, Object> findConditionParameterMap,
            CompiledCondition compiledCondition) {
        String selectQuery = this.resolveTableName(this.queryConfigurationEntry.getRecordSelectQuery());
        String condition = ((RDBMSCompiledCondition) compiledCondition).getCompiledQuery();
        Connection conn = this.getConnection();
        PreparedStatement stmt = null;
        ResultSet rs;
        try {
            stmt = RDBMSTableUtils.isEmpty(condition)
                    ? conn.prepareStatement(selectQuery.replace(PLACEHOLDER_CONDITION, ""))
                    : conn.prepareStatement(RDBMSTableUtils.formatQueryWithCondition(selectQuery, condition));
            RDBMSTableUtils.resolveCondition(stmt, (RDBMSCompiledCondition) compiledCondition,
                    findConditionParameterMap, 0);
            rs = stmt.executeQuery();
            //Passing all java.sql artifacts to the iterator to ensure everything gets cleaned up at once.
            return new RDBMSIterator(conn, stmt, rs, this.attributes, this.tableName);
        } catch (SQLException e) {
            RDBMSTableUtils.cleanupConnection(null, stmt, conn);
            throw new RDBMSTableException(
                    "Error retrieving records from table '" + this.tableName + "': " + e.getMessage(), e);
        }
    }

    @Override
    protected boolean contains(Map<String, Object> containsConditionParameterMap,
            CompiledCondition compiledCondition) {
        String containsQuery = this.resolveTableName(this.queryConfigurationEntry.getRecordExistsQuery());
        String condition = ((RDBMSCompiledCondition) compiledCondition).getCompiledQuery();
        Connection conn = this.getConnection();
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            stmt = RDBMSTableUtils.isEmpty(condition)
                    ? conn.prepareStatement(containsQuery.replace(PLACEHOLDER_CONDITION, ""))
                    : conn.prepareStatement(RDBMSTableUtils.formatQueryWithCondition(containsQuery, condition));
            RDBMSTableUtils.resolveCondition(stmt, (RDBMSCompiledCondition) compiledCondition,
                    containsConditionParameterMap, 0);
            rs = stmt.executeQuery();
            return rs.next() && !rs.isBeforeFirst();
        } catch (SQLException e) {
            throw new RDBMSTableException(
                    "Error performing a contains check on table '" + this.tableName + "': " + e.getMessage(), e);
        } finally {
            RDBMSTableUtils.cleanupConnection(rs, stmt, conn);
        }
    }

    @Override
    protected void delete(List<Map<String, Object>> deleteConditionParameterMaps,
            CompiledCondition compiledCondition) {
        String deleteQuery = this.resolveTableName(this.queryConfigurationEntry.getRecordDeleteQuery());
        String condition = ((RDBMSCompiledCondition) compiledCondition).getCompiledQuery();
        Connection conn = this.getConnection();
        PreparedStatement stmt = null;
        try {
            stmt = RDBMSTableUtils.isEmpty(condition)
                    ? conn.prepareStatement(deleteQuery.replace(PLACEHOLDER_CONDITION, ""))
                    : conn.prepareStatement(RDBMSTableUtils.formatQueryWithCondition(deleteQuery, condition));
            for (Map<String, Object> deleteConditionParameterMap : deleteConditionParameterMaps) {
                RDBMSTableUtils.resolveCondition(stmt, (RDBMSCompiledCondition) compiledCondition,
                        deleteConditionParameterMap, 0);
                stmt.addBatch();
            }
            stmt.executeBatch();
        } catch (SQLException e) {
            throw new RDBMSTableException(
                    "Error performing record deletion on table '" + this.tableName + "': " + e.getMessage(), e);
        } finally {
            RDBMSTableUtils.cleanupConnection(null, stmt, conn);
        }
    }

    @Override
    protected void update(List<Map<String, Object>> updateConditionParameterMaps,
            CompiledCondition compiledCondition, List<Map<String, Object>> updateValues) {
        String sql = this.composeUpdateQuery(compiledCondition);
        try {
            this.batchProcessUpdates(sql, updateConditionParameterMaps, compiledCondition, updateValues);
        } catch (SQLException e) {
            throw new RDBMSTableException("Error performing record update operations on table '" + this.tableName
                    + "': " + e.getMessage(), e);
        }
    }

    @Override
    protected void updateOrAdd(List<Map<String, Object>> updateConditionParameterMaps,
            CompiledCondition compiledCondition, List<Map<String, Object>> updateValues,
            List<Object[]> addingRecords) {
        this.updateOrInsertRecords(updateConditionParameterMaps, compiledCondition, updateValues, addingRecords);
    }

    @Override
    protected CompiledCondition compileCondition(ConditionBuilder conditionBuilder) {
        RDBMSConditionVisitor visitor = new RDBMSConditionVisitor(this.tableName);
        conditionBuilder.build(visitor);
        return new RDBMSCompiledCondition(visitor.returnCondition(), visitor.getParameters());
    }

    /**
     * Method for looking up a datasource instance through JNDI.
     *
     * @param resourceName the name of the resource to be looked up.
     * @throws NamingException if the lookup fails.
     */
    private void lookupDatasource(String resourceName) throws NamingException {
        this.dataSource = InitialContext.doLookup(resourceName);
    }

    /**
     * Method for composing the SQL query for INSERT operations with proper placeholders.
     *
     * @return the composed SQL query in string form.
     */
    private String composeInsertQuery() {
        String insertQuery = this.resolveTableName(this.queryConfigurationEntry.getRecordInsertQuery());
        StringBuilder params = new StringBuilder();
        int fieldsLeft = this.attributes.size();
        while (fieldsLeft > 0) {
            params.append(QUESTION_MARK);
            if (fieldsLeft > 1) {
                params.append(SEPARATOR);
            }
            fieldsLeft = fieldsLeft - 1;
        }
        return insertQuery.replace(PLACEHOLDER_Q, params.toString());
    }

    /**
     * Method for composing the SQL query for UPDATE operations with proper placeholders.
     *
     * @return the composed SQL query in string form.
     */
    private String composeUpdateQuery(CompiledCondition compiledCondition) {
        String sql = this.resolveTableName(this.queryConfigurationEntry.getRecordUpdateQuery());
        String condition = ((RDBMSCompiledCondition) compiledCondition).getCompiledQuery();
        StringBuilder columnsValues = new StringBuilder();
        this.attributes.forEach(attribute -> {
            columnsValues.append(attribute.getName()).append(EQUALS).append(QUESTION_MARK);
            if (this.attributes.indexOf(attribute) != this.attributes.size() - 1) {
                columnsValues.append(SEPARATOR);
            }
        });
        sql = sql.replace(PLACEHOLDER_COLUMNS_VALUES, columnsValues.toString());
        sql = RDBMSTableUtils.isEmpty(condition) ? sql.replace(PLACEHOLDER_CONDITION, "")
                : RDBMSTableUtils.formatQueryWithCondition(sql, condition);
        return sql;
    }

    /**
     * Method for processing update operations in a batched manner. This assumes that all update operations will be
     * accepted by the database.
     *
     * @param sql                          the SQL update operation as string.
     * @param updateConditionParameterMaps the runtime parameters that should be populated to the condition.
     * @param compiledCondition            the condition that was built during compile time.
     * @param updateValues                 the runtime parameters that should be populated to the update statement.
     * @throws SQLException if the update operation fails.
     */
    private void batchProcessUpdates(String sql, List<Map<String, Object>> updateConditionParameterMaps,
            CompiledCondition compiledCondition, List<Map<String, Object>> updateValues) throws SQLException {
        final int seed = this.attributes.size();
        Connection conn = this.getConnection();
        PreparedStatement stmt = null;
        try {
            stmt = conn.prepareStatement(sql);
            Iterator<Map<String, Object>> conditionParamIterator = updateConditionParameterMaps.iterator();
            Iterator<Map<String, Object>> valueIterator = updateValues.iterator();
            while (conditionParamIterator.hasNext() && valueIterator.hasNext()) {
                Map<String, Object> conditionParameters = conditionParamIterator.next();
                Map<String, Object> values = valueIterator.next();
                //Incrementing the ordinals of the conditions in the statement with the # of variables to be updated
                RDBMSTableUtils.resolveCondition(stmt, (RDBMSCompiledCondition) compiledCondition,
                        conditionParameters, seed);
                for (Attribute attribute : this.attributes) {
                    RDBMSTableUtils.populateStatementWithSingleElement(stmt, this.attributes.indexOf(attribute) + 1,
                            attribute.getType(), values.get(attribute.getName()));
                }
                stmt.addBatch();
            }
            stmt.executeBatch();
        } finally {
            RDBMSTableUtils.cleanupConnection(null, stmt, conn);
        }
    }

    /**
     * Method for performing insert/update operations for a given dataset.
     *
     * @param updateConditionParameterMaps update parameters that should be populated for each condition.
     * @param compiledCondition            the condition that was built during compile time.
     * @param updateValues                 the values for which the update operation should be done
     *                                     (i.e. the new values).
     * @param addingRecords                the records that should be inserted to the DB should the update operation
     *                                     fail.
     */
    private void updateOrInsertRecords(List<Map<String, Object>> updateConditionParameterMaps,
            CompiledCondition compiledCondition, List<Map<String, Object>> updateValues,
            List<Object[]> addingRecords) {
        int counter = 0;
        final int seed = this.attributes.size();
        Connection conn = this.getConnection(false);
        PreparedStatement updateStmt = null;
        PreparedStatement insertStmt = null;
        try {
            updateStmt = conn.prepareStatement(this.composeUpdateQuery(compiledCondition));
            insertStmt = conn.prepareStatement(this.composeInsertQuery());
            while (counter < updateValues.size()) {
                int rowsChanged;
                Map<String, Object> conditionParameters = updateConditionParameterMaps.get(counter);
                Map<String, Object> values = updateValues.get(counter);
                //Incrementing the ordinals of the conditions in the statement with the # of variables to be updated
                RDBMSTableUtils.resolveCondition(updateStmt, (RDBMSCompiledCondition) compiledCondition,
                        conditionParameters, seed);
                for (Attribute attribute : this.attributes) {
                    RDBMSTableUtils.populateStatementWithSingleElement(updateStmt,
                            this.attributes.indexOf(attribute) + 1, attribute.getType(),
                            values.get(attribute.getName()));
                }
                rowsChanged = updateStmt.executeUpdate();
                conn.commit();
                if (rowsChanged < 1) {
                    Object[] record = addingRecords.get(counter);
                    try {
                        this.populateStatement(record, insertStmt);
                        insertStmt.executeUpdate();
                        conn.commit();
                    } catch (SQLException e2) {
                        RDBMSTableUtils.rollbackConnection(conn);
                        throw new RDBMSTableException("Error performing update/insert operation (insert) on table '"
                                + this.tableName + "': " + e2.getMessage(), e2);
                    }
                }
                counter++;
            }
        } catch (SQLException e) {
            throw new RDBMSTableException("Error performing update/insert operation (update) on table '"
                    + this.tableName + "': " + e.getMessage(), e);
        } finally {
            RDBMSTableUtils.cleanupConnection(null, updateStmt, null);
            RDBMSTableUtils.cleanupConnection(null, insertStmt, conn);
        }
    }

    /**
     * Method for creating and initializing the datasource instance given the "@Store" annotation.
     *
     * @param storeAnnotation the source annotation which contains the needed parameters.
     */
    private void initializeDatasource(Annotation storeAnnotation) {
        Properties connectionProperties = new Properties();
        String poolPropertyString = storeAnnotation.getElement(ANNOTATION_ELEMENT_POOL_PROPERTIES);
        String url = storeAnnotation.getElement(ANNOTATION_ELEMENT_URL);
        String username = storeAnnotation.getElement(ANNOTATION_ELEMENT_USERNAME);
        String password = storeAnnotation.getElement(ANNOTATION_ELEMENT_PASSWORD);
        if (RDBMSTableUtils.isEmpty(url)) {
            throw new RDBMSTableException("Required parameter '" + ANNOTATION_ELEMENT_URL + "' for DB "
                    + "connectivity cannot be empty.");
        }
        if (RDBMSTableUtils.isEmpty(username)) {
            throw new RDBMSTableException("Required parameter '" + ANNOTATION_ELEMENT_USERNAME + "' for DB "
                    + "connectivity cannot be empty.");
        }
        if (RDBMSTableUtils.isEmpty(password)) {
            throw new RDBMSTableException("Required parameter '" + ANNOTATION_ELEMENT_PASSWORD + "' for DB "
                    + "connectivity cannot be empty.");
        }
        connectionProperties.setProperty("jdbcUrl", url);
        connectionProperties.setProperty("dataSource.user", username);
        connectionProperties.setProperty("dataSource.password", password);
        if (poolPropertyString != null) {
            List<String[]> poolProps = RDBMSTableUtils.processKeyValuePairs(poolPropertyString);
            poolProps.forEach(pair -> connectionProperties.setProperty(pair[0], pair[1]));
        }
        HikariConfig config = new HikariConfig(connectionProperties);
        this.dataSource = new HikariDataSource(config);
    }

    /**
     * Returns a connection instance assuming that autocommit should be true.
     *
     * @return a new {@link Connection} instance from the datasource.
     */
    private Connection getConnection() {
        return this.getConnection(true);
    }

    /**
     * Returns a connection instance.
     *
     * @param autoCommit whether or not transactions to the connections should be committed automatically.
     * @return a new {@link Connection} instance from the datasource.
     */
    private Connection getConnection(boolean autoCommit) {
        Connection conn;
        try {
            conn = this.dataSource.getConnection();
            conn.setAutoCommit(autoCommit);
        } catch (SQLException e) {
            throw new RDBMSTableException("Error initializing connection: " + e.getMessage(), e);
        }
        return conn;
    }

    /**
     * Method for replacing the placeholder for the table name with the Event Table's name.
     *
     * @param statement the SQL statement in string form.
     * @return the formatted SQL statement.
     */
    private String resolveTableName(String statement) {
        if (statement == null) {
            return null;
        }
        return statement.replace(PLACEHOLDER_TABLE_NAME, this.tableName);
    }

    /**
     * Method for creating a table on the data store in question, if it does not exist already.
     *
     * @param storeAnnotation the "@Store" annotation that contains the connection properties.
     * @param primaryKeys     the unique keys that should be set for the table.
     * @param indices         the DB indices that should be set for the table.
     */
    private void createTable(Annotation storeAnnotation, Annotation primaryKeys, Annotation indices) {
        RDBMSTypeMapping typeMapping = this.queryConfigurationEntry.getRdbmsTypeMapping();
        StringBuilder builder = new StringBuilder();
        List<Element> primaryKeyList = (primaryKeys == null) ? new ArrayList<>() : primaryKeys.getElements();
        List<Element> indexElementList = (indices == null) ? new ArrayList<>() : indices.getElements();
        List<String> queries = new ArrayList<>();
        String createQuery = this.resolveTableName(this.queryConfigurationEntry.getTableCreateQuery());
        String indexQuery = this.resolveTableName(this.queryConfigurationEntry.getIndexCreateQuery());
        Map<String, String> fieldLengths = RDBMSTableUtils
                .processFieldLengths(storeAnnotation.getElement(ANNOTATION_ELEMENT_FIELD_LENGTHS));
        this.validateFieldLengths(fieldLengths);
        this.attributes.forEach(attribute -> {
            builder.append(attribute.getName()).append(WHITESPACE);
            switch (attribute.getType()) {
            case BOOL:
                builder.append(typeMapping.getBooleanType());
                break;
            case DOUBLE:
                builder.append(typeMapping.getDoubleType());
                break;
            case FLOAT:
                builder.append(typeMapping.getFloatType());
                break;
            case INT:
                builder.append(typeMapping.getIntegerType());
                break;
            case LONG:
                builder.append(typeMapping.getLongType());
                break;
            case OBJECT:
                builder.append(typeMapping.getBinaryType());
                break;
            case STRING:
                builder.append(typeMapping.getStringType());
                if (this.queryConfigurationEntry.getStringSize() != null) {
                    builder.append(OPEN_PARENTHESIS);
                    if (fieldLengths.containsKey(attribute.getName())) {
                        builder.append(fieldLengths.get(attribute.getName()));
                    } else {
                        builder.append(this.queryConfigurationEntry.getStringSize());
                    }
                    builder.append(CLOSE_PARENTHESIS);
                }
                break;
            }
            if (this.attributes.indexOf(attribute) != this.attributes.size() - 1 || !primaryKeyList.isEmpty()) {
                builder.append(SEPARATOR);
            }
        });
        if (primaryKeyList != null && !primaryKeyList.isEmpty()) {
            builder.append(SQL_PRIMARY_KEY_DEF).append(OPEN_PARENTHESIS)
                    .append(RDBMSTableUtils.flattenAnnotatedElements(primaryKeyList)).append(CLOSE_PARENTHESIS);
        }
        queries.add(createQuery.replace(PLACEHOLDER_COLUMNS, builder.toString()));
        if (indexElementList != null && !indexElementList.isEmpty()) {
            queries.add(indexQuery.replace(PLACEHOLDER_INDEX,
                    RDBMSTableUtils.flattenAnnotatedElements(indexElementList)));
        }
        try {
            this.executeDDQueries(queries, false);
        } catch (SQLException e) {
            throw new RDBMSTableException("Unable to initialize table '" + this.tableName + "': " + e.getMessage(),
                    e);
        }
    }

    /**
     * Method used to validate the field length specifications and ensure that the table definition contains them.
     *
     * @param fieldLengths the specified list of custom string field lengths.
     */
    private void validateFieldLengths(Map<String, String> fieldLengths) {
        List<String> attributeNames = new ArrayList<>();
        this.attributes.forEach(attribute -> attributeNames.add(attribute.getName()));
        fieldLengths.keySet().forEach(field -> {
            if (!attributeNames.contains(field)) {
                throw new RDBMSTableException(
                        "Field '" + field + "' (for which a size of " + fieldLengths.get(field)
                                + " has been specified) does not exist in the table's list of fields.");
            }
        });
    }

    /**
     * Method for performing data definition queries for the current datasource.
     *
     * @param queries    the list of queries to be executed.
     * @param autocommit whether or not the transactions should automatically be committed.
     * @throws SQLException if the query execution fails.
     */
    private void executeDDQueries(List<String> queries, boolean autocommit) throws SQLException {
        Connection conn = this.getConnection(autocommit);
        boolean committed = autocommit;
        PreparedStatement stmt;
        try {
            for (String query : queries) {
                stmt = conn.prepareStatement(query);
                stmt.execute();
                RDBMSTableUtils.cleanupConnection(null, stmt, null);
            }
            if (!autocommit) {
                conn.commit();
                committed = true;
            }
        } catch (SQLException e) {
            if (!autocommit) {
                RDBMSTableUtils.rollbackConnection(conn);
            }
            throw e;
        } finally {
            if (!committed) {
                RDBMSTableUtils.rollbackConnection(conn);
            }
            RDBMSTableUtils.cleanupConnection(null, null, conn);
        }
    }

    /**
     * Given a set of records and a query, this method performs that query per each record.
     *
     * @param query      the query to be executed.
     * @param records    the records to use.
     * @param autocommit whether or not the transactions should automatically be committed.
     * @throws SQLException if the query execution fails.
     */
    private void batchExecuteQueriesWithRecords(String query, List<Object[]> records, boolean autocommit)
            throws SQLException {
        PreparedStatement stmt = null;
        boolean committed = autocommit;
        //TODO check if autocommit needs to be false (e.g. for Postgres case)
        Connection conn = this.getConnection(autocommit);
        try {
            stmt = conn.prepareStatement(query);
            for (Object[] record : records) {
                this.populateStatement(record, stmt);
                stmt.addBatch();
            }
            stmt.executeBatch();
            if (!autocommit) {
                conn.commit();
                committed = true;
            }
        } catch (SQLException e) {
            if (log.isDebugEnabled()) {
                log.debug("Attempted execution of query [" + query + "] produced an exception: " + e.getMessage());
            }
            if (!autocommit) {
                RDBMSTableUtils.rollbackConnection(conn);
            }
            throw e;
        } finally {
            if (!committed) {
                RDBMSTableUtils.rollbackConnection(conn);
            }
            RDBMSTableUtils.cleanupConnection(null, stmt, conn);
        }
    }

    /**
     * Method for checking whether or not the given table (which reflects the current event table instance) exists.
     *
     * @return true/false based on the table existence.
     */
    private boolean tableExists() {
        Connection conn = this.getConnection();
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            String query = this.resolveTableName(this.queryConfigurationEntry.getTableCheckQuery());
            stmt = conn.prepareStatement(query);
            rs = stmt.executeQuery();
            return true;
        } catch (SQLException e) {
            if (log.isDebugEnabled()) {
                log.debug("Table '" + this.tableName + "' assumed to not exist since its existence check resulted "
                        + "in exception " + e.getMessage());
            }
            return false;
        } finally {
            RDBMSTableUtils.cleanupConnection(rs, stmt, conn);
        }
    }

    /**
     * Method for populating values to a pre-created SQL prepared statement.
     *
     * @param record the record whose values should be populated.
     * @param stmt   the statement to which the values should be set.
     */
    private void populateStatement(Object[] record, PreparedStatement stmt) {
        Attribute attribute = null;
        try {
            for (int i = 0; i < this.attributes.size(); i++) {
                attribute = this.attributes.get(i);
                Object value = record[i];
                if (value != null || attribute.getType() == Attribute.Type.STRING) {
                    RDBMSTableUtils.populateStatementWithSingleElement(stmt, i + 1, attribute.getType(), value);
                } else {
                    throw new RDBMSTableException("Cannot Execute Insert/Update: null value detected for "
                            + "attribute '" + attribute.getName() + "'");
                }
            }
        } catch (SQLException e) {
            throw new RDBMSTableException("Dropping event since value for attribute name " + attribute.getName()
                    + "cannot be set: " + e.getMessage(), e);
        }
    }
}