All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datanucleus.store.cassandra.CassandraSchemaHandler Maven / Gradle / Ivy

There is a newer version: 6.0.0-release
Show newest version
/**********************************************************************
Copyright (c) 2014 Andy Jefferson and others. 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.

Contributors:
    ...
**********************************************************************/
package org.datanucleus.store.cassandra;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.PropertyNames;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.ClassMetaData;
import org.datanucleus.metadata.ClassPersistenceModifier;
import org.datanucleus.metadata.DiscriminatorMetaData;
import org.datanucleus.metadata.IndexMetaData;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.store.StoreData;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.schema.AbstractStoreSchemaHandler;
import org.datanucleus.store.schema.naming.ColumnType;
import org.datanucleus.store.schema.naming.NamingFactory;
import org.datanucleus.store.schema.table.Column;
import org.datanucleus.store.schema.table.CompleteClassTable;
import org.datanucleus.store.schema.table.MemberColumnMapping;
import org.datanucleus.store.schema.table.Table;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;

/**
 * Handler for schema management with Cassandra.
 */
public class CassandraSchemaHandler extends AbstractStoreSchemaHandler
{
    protected static final Localiser LOCALISER_CASSANDRA = Localiser.getInstance(
        "org.datanucleus.store.cassandra.Localisation", CassandraStoreManager.class.getClassLoader());

    CassandraStoreManager casStoreMgr;

    public CassandraSchemaHandler(CassandraStoreManager storeMgr)
    {
        super(storeMgr);
        this.casStoreMgr = (CassandraStoreManager)storeMgr;
        // TODO Check if the schema exists and create it according to isAutoCreateSchema()
    }

    /**
     * Method to create a schema (keyspace) in Cassandra.
     * Accepts properties with names "replication", "durable_writes" (case sensitive).
     * @param schemaName Name of the schema
     * @param props Any properties defining the new keyspace
     */
    public void createSchema(String schemaName, Properties props, Object connection)
    {
        Session session = (Session)connection;
        ManagedConnection mconn = null;
        try
        {
            if (session == null)
            {
                mconn = storeMgr.getConnection(-1);
                session = (Session)mconn.getConnection();
            }

            StringBuilder stmtBuilder = new StringBuilder("CREATE KEYSPACE IF NOT EXISTS ");
            stmtBuilder.append(schemaName).append(" WITH ");
            String replicationProp = (props != null ? (String)props.get("replication") : "{'class': 'SimpleStrategy', 'replication_factor' : 3}");
            stmtBuilder.append("replication = ").append(replicationProp);
            if (props != null && props.containsKey("durable_writes"))
            {
                Boolean durable = Boolean.valueOf((String)props.get("durable_writes"));
                if (!durable)
                {
                    stmtBuilder.append(" AND durable_writes=false");
                }
            }

            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateSchema", stmtBuilder.toString()));
            session.execute(stmtBuilder.toString());
            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateSchema.Success"));
        }
        finally
        {
            if (mconn != null)
            {
                mconn.release();
            }
        }
    }

    public void createSchemaForClasses(Set classNames, Properties props, Object connection)
    {
        Session session = (Session)connection;
        String ddlFilename = props != null ? props.getProperty("ddlFilename") : null;
        //        String completeDdlProp = props != null ? props.getProperty("completeDdl") : null;
        //        boolean completeDdl = (completeDdlProp != null && completeDdlProp.equalsIgnoreCase("true"));

        FileWriter ddlFileWriter = null;
        try
        {
            if (ddlFilename != null)
            {
                // Open the DDL file for writing
                File ddlFile = StringUtils.getFileForFilename(ddlFilename);
                if (ddlFile.exists())
                {
                    // Delete existing file
                    ddlFile.delete();
                }
                if (ddlFile.getParentFile() != null && !ddlFile.getParentFile().exists())
                {
                    // Make sure the directory exists
                    ddlFile.getParentFile().mkdirs();
                }
                ddlFile.createNewFile();
                ddlFileWriter = new FileWriter(ddlFile);

                SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
                ddlFileWriter.write("------------------------------------------------------------------\n");
                ddlFileWriter.write("-- DataNucleus SchemaTool " + 
                    "(ran at " + fmt.format(new java.util.Date()) + ")\n");
                ddlFileWriter.write("------------------------------------------------------------------\n");
            }

            ManagedConnection mconn = null;
            try
            {
                if (session == null)
                {
                    mconn = storeMgr.getConnection(-1);
                    session = (Session)mconn.getConnection();
                }

                // Allocate Lists for holding the required CQL statements needed for these classes
                List tableStmts = new ArrayList();
                List constraintStmts = new ArrayList();

                Iterator classIter = classNames.iterator();
                ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
                while (classIter.hasNext())
                {
                    String className = classIter.next();
                    AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr);
                    if (cmd != null)
                    {
                        createSchemaForClass(cmd, session, clr, tableStmts, constraintStmts);
                    }
                }

                if (!tableStmts.isEmpty())
                {
                    // Process the required schema updates for tables
                    for (String stmt : tableStmts)
                    {
                        if (ddlFileWriter == null)
                        {
                            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateTable", stmt));
                            session.execute(stmt);
                            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateTable.Success"));
                        }
                        else
                        {
                            try
                            {
                                ddlFileWriter.write(stmt + ";\n");
                            }
                            catch (IOException ioe)
                            {}
                        }
                    }
                }
                if (!constraintStmts.isEmpty())
                {
                    // Process the required schema updates for constraints
                    for (String stmt : constraintStmts)
                    {
                        if (ddlFileWriter == null)
                        {
                            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateConstraint", stmt));
                            session.execute(stmt);
                            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CreateConstraint.Success"));
                        }
                        else
                        {
                            try
                            {
                                ddlFileWriter.write(stmt + ";\n");
                            }
                            catch (IOException ioe)
                            {}
                        }
                    }
                }
            }
            finally
            {
                if (mconn != null)
                {
                    mconn.release();
                }
            }
        }
        catch (IOException ioe)
        {
            // Error in writing DDL file
            // TODO Handle this
        }
        finally
        {
            if (ddlFileWriter != null)
            {
                try
                {
                    ddlFileWriter.close();
                }
                catch (IOException ioe)
                {
                    // Error in close of DDL
                }
            }
        }
    }

    /**
     * Method to generate the necessary CQL to create the schema (table/indexes) for the specified class.
     * @param cmd Metadata for the class
     * @param session Session to use for checking of existence in datastore
     * @param clr ClassLoader resolver
     * @param tableStmts List to add any table CQL statements to
     * @param constraintStmts List to add any constraint CQL statements to
     */
    protected void createSchemaForClass(AbstractClassMetaData cmd, Session session, ClassLoaderResolver clr, List tableStmts, List constraintStmts)
    {
        if (cmd.isEmbeddedOnly() || cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE)
        {
            // No table required here
            return;
        }
        if (cmd instanceof ClassMetaData && ((ClassMetaData)cmd).isAbstract())
        {
            // No table required here
            return;
        }

        StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName());
        Table table = null;
        if (storeData != null)
        {
            table = (Table) storeData.getProperties().get("tableObject");
        }
        else
        {
            table = new CompleteClassTable(storeMgr, cmd, new SchemaVerifierImpl(storeMgr, cmd, clr));
        }
        // TODO Check existence of schema using "select keyspace_name from system.schema_keyspaces where keyspace_name='schema1';"
        String schemaName = table.getSchemaName();

        SessionStatementProvider stmtProvider = ((CassandraStoreManager)storeMgr).getStatementProvider();
        if (checkTableExistence(session, stmtProvider, schemaName, table.getIdentifier()))
        {
            // Add/delete any columns to match the current definition (aka "schema evolution")
            Map tableStructure = getColumnDetailsForTable(session, stmtProvider, schemaName, table.getIdentifier());

            List columns = table.getColumns();
            for (Column column : columns)
            {
                ColumnDetails colDetails = getColumnDetailsForColumn(column, tableStructure);
                if (colDetails == null)
                {
                    // Add column since doesn't exist
                    StringBuilder stmtBuilder = new StringBuilder("ALTER TABLE ");
                    if (schemaName != null)
                    {
                        stmtBuilder.append(schemaName).append('.');
                    }
                    stmtBuilder.append(table.getIdentifier());
                    stmtBuilder.append(" ADD COLUMN ");
                    stmtBuilder.append(column.getIdentifier()).append(" ").append(column.getTypeName());
                    tableStmts.add(stmtBuilder.toString());
                }
                else
                {
                    if (colDetails.typeName != null && !colDetails.typeName.equals(column.getTypeName()))
                    {
                        // TODO Change the column type if requested. What about existing data
                        NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.TableColumnTypeIncorrect", table.getIdentifier(), 
                            column.getIdentifier(), colDetails.typeName, column.getTypeName()));
                    }
                }
            }
            // TODO Cycle through the current columns and check if any are not needed by this class

            if (isAutoCreateConstraints())
            {
                // Class-level indexes
                NamingFactory namingFactory = storeMgr.getNamingFactory();
                AbstractClassMetaData theCmd = cmd;
                while (theCmd != null)
                {
                    IndexMetaData[] clsIdxMds = theCmd.getIndexMetaData();
                    if (clsIdxMds != null)
                    {
                        for (int i=0;i 1)
                            {
                                NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexForClassWithMultipleColumns", theCmd.getFullClassName()));
                            }
                            else
                            {
                                ColumnDetails colDetails = tableStructure.get(colNames[0]);
                                String idxName = namingFactory.getIndexName(theCmd, idxmd, i);
                                if (colDetails == null)
                                {
                                    // Add index
                                    String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), colNames[0]);
                                    constraintStmts.add(indexStmt);
                                }
                                else
                                {
                                    if (!idxName.equals(colDetails.indexName))
                                    {
                                        NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexHasWrongName", idxName, colDetails.indexName));
                                    }
                                }
                            }
                        }
                    }
                    theCmd = theCmd.getSuperAbstractClassMetaData();
                }

                // Column-level indexes
                Set mappings = table.getMemberColumnMappings();
                for (MemberColumnMapping mapping : mappings)
                {
                    IndexMetaData idxmd = mapping.getMemberMetaData().getIndexMetaData();
                    if (idxmd != null)
                    {
                        String[] colNames = idxmd.getColumnNames();
                        if (colNames.length > 1)
                        {
                            NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexForMemberWithMultipleColumns", mapping.getMemberMetaData().getFullFieldName()));
                        }
                        else
                        {
                            if (mapping.getNumberOfColumns() == 1)
                            {
                                Column column = mapping.getColumn(0);
                                ColumnDetails colDetails = getColumnDetailsForColumn(column, tableStructure);
                                String idxName = namingFactory.getIndexName(mapping.getMemberMetaData(), idxmd);
                                if (colDetails == null)
                                {
                                    // Add index
                                    String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), column.getIdentifier());
                                    constraintStmts.add(indexStmt);
                                }
                                else
                                {
                                    if (!idxName.equals(colDetails.indexName))
                                    {
                                        NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexHasWrongName", idxName, colDetails.indexName));
                                    }
                                }
                            }
                        }
                    }
                }

                if (cmd.isVersioned() && cmd.getVersionMetaDataForClass() != null && cmd.getVersionMetaDataForClass().getFieldName() == null)
                {
                    VersionMetaData vermd = cmd.getVersionMetaDataForClass();
                    if (vermd.getIndexMetaData() != null)
                    {
                        Column column = table.getVersionColumn();
                        ColumnDetails colDetails = getColumnDetailsForColumn(column, tableStructure);
                        if (colDetails == null)
                        {
                            String idxName = namingFactory.getIndexName(cmd, vermd.getIndexMetaData(), ColumnType.VERSION_COLUMN);
                            String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), column.getIdentifier());
                            constraintStmts.add(indexStmt);
                        }
                        else
                        {
                            String idxName = namingFactory.getIndexName(cmd, vermd.getIndexMetaData(), ColumnType.VERSION_COLUMN);
                            if (!idxName.equals(colDetails.indexName))
                            {
                                NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexHasWrongName", idxName, colDetails.indexName));
                            }
                        }
                    }
                }
                if (cmd.hasDiscriminatorStrategy())
                {
                    DiscriminatorMetaData dismd = cmd.getDiscriminatorMetaData();
                    if (dismd.getIndexMetaData() != null)
                    {
                        Column column = table.getDiscriminatorColumn();
                        ColumnDetails colDetails = getColumnDetailsForColumn(column, tableStructure);
                        if (colDetails == null)
                        {
                            String idxName = namingFactory.getIndexName(cmd, dismd.getIndexMetaData(), ColumnType.DISCRIMINATOR_COLUMN);
                            String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), column.getIdentifier());
                            constraintStmts.add(indexStmt);
                        }
                        else
                        {
                            String idxName = namingFactory.getIndexName(cmd, dismd.getIndexMetaData(), ColumnType.DISCRIMINATOR_COLUMN);
                            if (!idxName.equals(colDetails.indexName))
                            {
                                NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexHasWrongName", idxName, colDetails.indexName));
                            }
                        }
                    }
                }
                if (storeMgr.getStringProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID) != null && !"true".equalsIgnoreCase(cmd.getValueForExtension("multitenancy-disable")))
                {
                    // TODO Add index on multitenancy discriminator
                }
            }
        }
        else
        {
            if (isAutoCreateTables())
            {
                // Create the table required for this class "CREATE TABLE keyspace.tblName (col1 type1, col2 type2, ...)"
                StringBuilder stmtBuilder = new StringBuilder("CREATE TABLE "); // Note that we could do "IF NOT EXISTS" but have the existence checker method for validation so use that
                if (schemaName != null)
                {
                    stmtBuilder.append(schemaName).append('.');
                }
                stmtBuilder.append(table.getIdentifier());
                stmtBuilder.append(" (");

                List pkColNames = new ArrayList();
                for (Column column : table.getColumns())
                {
                    stmtBuilder.append(column.getIdentifier()).append(' ').append(column.getTypeName()).append(',');
                    if (column.isPrimaryKey())
                    {
                        pkColNames.add(column.getIdentifier());
                    }
                }

                stmtBuilder.append(" PRIMARY KEY (");
                Iterator pkColNameIter = pkColNames.iterator();
                while (pkColNameIter.hasNext())
                {
                    stmtBuilder.append(pkColNameIter.next());
                    if (pkColNameIter.hasNext())
                    {
                        stmtBuilder.append(',');
                    }
                }
                stmtBuilder.append(")");

                stmtBuilder.append(")");
                // TODO Add support for "WITH option1=val1 AND option2=val2 ..." by using extensions part of metadata
                tableStmts.add(stmtBuilder.toString());
            }

            if (isAutoCreateConstraints())
            {
                NamingFactory namingFactory = storeMgr.getNamingFactory();

                // Add class-level indexes, including those defined for superclasses (since we hold the fields of those classes too)
                AbstractClassMetaData theCmd = cmd;
                while (theCmd != null)
                {
                    IndexMetaData[] clsIdxMds = theCmd.getIndexMetaData();
                    if (clsIdxMds != null)
                    {
                        for (int i=0;i 1)
                            {
                                NucleusLogger.DATASTORE_SCHEMA.warn(LOCALISER_CASSANDRA.msg("Cassandra.Schema.IndexForClassWithMultipleColumns", theCmd.getFullClassName()));
                            }
                            else
                            {
                                String idxName = namingFactory.getIndexName(theCmd, idxmd, i);
                                String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), colNames[0]);
                                constraintStmts.add(indexStmt);
                            }
                        }
                    }
                    theCmd = theCmd.getSuperAbstractClassMetaData();
                }

                // Add column-level indexes
                Set mappings = table.getMemberColumnMappings();
                for (MemberColumnMapping mapping : mappings)
                {
                    AbstractMemberMetaData mmd = mapping.getMemberMetaData();
                    IndexMetaData idxmd = mmd.getIndexMetaData();
                    if (idxmd != null)
                    {
                        if (mapping.getNumberOfColumns() == 1)
                        {
                            // Index specified on this member, so add it TODO Add check if member has multiple columns
                            String idxName = namingFactory.getIndexName(mmd, idxmd);
                            String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), mapping.getColumn(0).getIdentifier());
                            constraintStmts.add(indexStmt);
                        }
                    }
                }

                if (cmd.isVersioned() && cmd.getVersionMetaDataForClass() != null && cmd.getVersionMetaDataForClass().getFieldName() == null)
                {
                    VersionMetaData vermd = cmd.getVersionMetaDataForClass();
                    if (vermd.getIndexMetaData() != null)
                    {
                        Column column = table.getVersionColumn();
                        String idxName = namingFactory.getIndexName(cmd, vermd.getIndexMetaData(), ColumnType.VERSION_COLUMN);
                        String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), column.getIdentifier());
                        constraintStmts.add(indexStmt);
                    }
                }
                if (cmd.hasDiscriminatorStrategy())
                {
                    DiscriminatorMetaData dismd = cmd.getDiscriminatorMetaData();
                    if (dismd.getIndexMetaData() != null)
                    {
                        Column column = table.getDiscriminatorColumn();
                        String idxName = namingFactory.getIndexName(cmd, dismd.getIndexMetaData(), ColumnType.DISCRIMINATOR_COLUMN);
                        String indexStmt = createIndexCQL(idxName, schemaName, table.getIdentifier(), column.getIdentifier());
                        constraintStmts.add(indexStmt);
                    }
                }
                if (storeMgr.getStringProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID) != null && !"true".equalsIgnoreCase(cmd.getValueForExtension("multitenancy-disable")))
                {
                    // TODO Add index on multitenancy discriminator
                }
            }
        }
    }

    /**
     * Method to drop a schema (keyspace) in Cassandra.
     * @param schemaName Name of the schema (keyspace).
     * @param props Any properties controlling deletion
     * @param connection Connection to use (null implies this will obtain its own connection)
     */
    public void deleteSchema(String schemaName, Properties props, Object connection)
    {
        Session session = (Session)connection;
        ManagedConnection mconn = null;
        try
        {
            if (session == null)
            {
                mconn = storeMgr.getConnection(-1);
                session = (Session)mconn.getConnection();
            }

            StringBuilder stmtBuilder = new StringBuilder("DROP KEYSPACE IF EXISTS ");
            stmtBuilder.append(schemaName);

            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.DropSchema", stmtBuilder.toString()));
            session.execute(stmtBuilder.toString());
            NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.DropSchema", schemaName));
        }
        finally
        {
            if (mconn != null)
            {
                mconn.release();
            }
        }
    }

    public void deleteSchemaForClasses(Set classNames, Properties props, Object connection)
    {
        Session session = (Session)connection;
        String ddlFilename = props != null ? props.getProperty("ddlFilename") : null;
//      String completeDdlProp = props != null ? props.getProperty("completeDdl") : null;
//      boolean completeDdl = (completeDdlProp != null && completeDdlProp.equalsIgnoreCase("true"));

        FileWriter ddlFileWriter = null;
        try
        {
            if (ddlFilename != null)
            {
                // Open the DDL file for writing
                File ddlFile = StringUtils.getFileForFilename(ddlFilename);
                if (ddlFile.exists())
                {
                    // Delete existing file
                    ddlFile.delete();
                }
                if (ddlFile.getParentFile() != null && !ddlFile.getParentFile().exists())
                {
                    // Make sure the directory exists
                    ddlFile.getParentFile().mkdirs();
                }
                ddlFile.createNewFile();
                ddlFileWriter = new FileWriter(ddlFile);

                SimpleDateFormat fmt = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
                ddlFileWriter.write("------------------------------------------------------------------\n");
                ddlFileWriter.write("-- DataNucleus SchemaTool " + 
                        "(ran at " + fmt.format(new java.util.Date()) + ")\n");
                ddlFileWriter.write("------------------------------------------------------------------\n");
            }

            // TODO Add deletion of any "incrementtable" if used

            ManagedConnection mconn = null;
            try
            {
                if (session == null)
                {
                    mconn = storeMgr.getConnection(-1);
                    session = (Session)mconn.getConnection();
                }

                Iterator classIter = classNames.iterator();
                ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
                while (classIter.hasNext())
                {
                    String className = classIter.next();
                    AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr);
                    if (cmd != null && !cmd.isEmbeddedOnly() && cmd.getPersistenceModifier() == ClassPersistenceModifier.PERSISTENCE_CAPABLE)
                    {
                        if (cmd instanceof ClassMetaData && ((ClassMetaData)cmd).isAbstract())
                        {
                            // No table required here
                            continue;
                        }

                        StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                        Table table = null;
                        if (storeData != null)
                        {
                            table = (Table) storeData.getProperties().get("tableObject");
                        }
                        else
                        {
                            table = new CompleteClassTable(storeMgr, cmd, new SchemaVerifierImpl(storeMgr, cmd, clr));
                        }

                        // TODO Check existence of schema using "select keyspace_name from system.schema_keyspaces where keyspace_name='schema1';"
                        String schemaName = table.getSchemaName();
                        String tableName = table.getIdentifier();

                        NamingFactory namingFactory = storeMgr.getNamingFactory();
                        SessionStatementProvider stmtProvider = ((CassandraStoreManager)storeMgr).getStatementProvider();
                        boolean tableExists = checkTableExistence(session, stmtProvider, schemaName, tableName);
                        if (tableExists)
                        {
                            // Drop any class indexes
                            AbstractClassMetaData theCmd = cmd;
                            while (theCmd != null)
                            {
                                IndexMetaData[] clsIdxMds = theCmd.getIndexMetaData();
                                if (clsIdxMds != null)
                                {
                                    for (int i=0;i classNames, Properties props, Object connection)
    {
        Session session = (Session)connection;

        boolean success = true;
        ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
        ManagedConnection mconn = null;
        try
        {
            if (session == null)
            {
                mconn = storeMgr.getConnection(-1);
                session = (Session)mconn.getConnection();
            }

            NamingFactory namingFactory = storeMgr.getNamingFactory();
            for (String className : classNames)
            {
                AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr);
                if (cmd.isEmbeddedOnly() || cmd.getPersistenceModifier() != ClassPersistenceModifier.PERSISTENCE_CAPABLE)
                {
                    continue;
                }
                if (cmd instanceof ClassMetaData && ((ClassMetaData)cmd).isAbstract())
                {
                    // No table required here
                    continue;
                }

                StoreData storeData = storeMgr.getStoreDataForClass(cmd.getFullClassName());
                Table table = null;
                if (storeData != null)
                {
                    table = (Table) storeData.getProperties().get("tableObject");
                }
                else
                {
                    table = new CompleteClassTable(storeMgr, cmd, new SchemaVerifierImpl(storeMgr, cmd, clr));
                }

                // TODO Check existence of schema using "select keyspace_name from system.schema_keyspaces where keyspace_name='schema1';"
                String schemaName = table.getSchemaName();
                String tableName = table.getIdentifier();

                SessionStatementProvider stmtProvider = ((CassandraStoreManager)storeMgr).getStatementProvider();
                boolean tableExists = checkTableExistence(session, stmtProvider, schemaName, tableName);
                if (!tableExists)
                {
                    NucleusLogger.DATASTORE_SCHEMA.error(LOCALISER_CASSANDRA.msg("Cassandra.Schema.TableDoesntExist", cmd.getFullClassName(), tableName, schemaName));
                    success = false;
                }
                else
                {
                    // Check structure of the table against the required members
                    Map tableStructure = getColumnDetailsForTable(session, stmtProvider, schemaName, tableName);
                    Set colsFound = new HashSet();

                    List columns = table.getColumns();
                    for (Column column : columns)
                    {
                        ColumnDetails colDetails = getColumnDetailsForColumn(column, tableStructure);
                        if (colDetails == null)
                        {
                            // Column not present, so log it and fail the validation
                            if (column.getMemberColumnMapping() != null)
                            {
                                NucleusLogger.DATASTORE_SCHEMA.error(LOCALISER_CASSANDRA.msg("Cassandra.Schema.ColumnForTableDoesntExist", tableName, column.getIdentifier(), 
                                    column.getMemberColumnMapping().getMemberMetaData().getFullFieldName()));
                            }
                            else
                            {
                                NucleusLogger.DATASTORE_SCHEMA.error(LOCALISER_CASSANDRA.msg("Cassandra.Schema.ColumnForTableInvalidType", tableName, column.getIdentifier(),
                                    column.getColumnType()));
                            }
                            success = false;
                        }
                        else
                        {
                            String datastoreType = colDetails.typeName;
                            if (column.getTypeName().equals(datastoreType))
                            {
                                // Type is correct
                            }
                            else
                            {
                                NucleusLogger.DATASTORE_SCHEMA.error(LOCALISER_CASSANDRA.msg("Cassandra.Schema.ColumnTypeIncorrect", tableName, column.getIdentifier(),
                                    colDetails.typeName, column.getTypeName()));
                            }
                        }
                    }

                    if (success && tableStructure.size() != colsFound.size())
                    {
                        NucleusLogger.DATASTORE_SCHEMA.error(LOCALISER_CASSANDRA.msg("Cassandra.Schema.ColumnCountIncorrect", tableName, colsFound.size(), tableStructure.size()));
                        success = false;
                    }

                    // Check class-level indexes
                    AbstractClassMetaData theCmd = cmd;
                    while (theCmd != null)
                    {
                        IndexMetaData[] clsIdxMds = theCmd.getIndexMetaData();
                        if (clsIdxMds != null)
                        {
                            for (int i=0;i getColumnDetailsForTable(Session session, SessionStatementProvider stmtProvider, String schemaName, String tableName)
    {
        if (schemaName == null)
        {
            throw new NucleusUserException("Schema must be specified for table=" + tableName + " in order to check its structure");
        }
        StringBuilder stmtBuilder = new StringBuilder("SELECT column_name, index_name, validator FROM system.schema_columns WHERE keyspace_name=? AND columnfamily_name=?");
        NucleusLogger.DATASTORE_SCHEMA.debug(LOCALISER_CASSANDRA.msg("Cassandra.Schema.CheckTableStructure", tableName, stmtBuilder.toString()));
        PreparedStatement stmt = stmtProvider.prepare(stmtBuilder.toString(), session);
        ResultSet rs = session.execute(stmt.bind(schemaName.toLowerCase(), tableName.toLowerCase()));
        Map cols = new HashMap();
        Iterator iter = rs.iterator();
        while (iter.hasNext())
        {
            Row row = iter.next();
            String typeName = null;
            String validator = row.getString("validator");
            if (validator.indexOf("LongType") >= 0)
            {
                typeName = "bigint";
            }
            else if (validator.indexOf("IntegerType") >= 0 || validator.indexOf("Int32Type") >= 0)
            {
                typeName = "int";
            }
            else if (validator.indexOf("DoubleType") >= 0)
            {
                typeName = "double";
            }
            else if (validator.indexOf("DecimalType") >= 0)
            {
                typeName = "decimal";
            }
            else if (validator.indexOf("FloatType") >= 0)
            {
                typeName = "float";
            }
            else if (validator.indexOf("BooleanType") >= 0)
            {
                typeName = "boolean";
            }
            else if (validator.indexOf("DateType") >= 0)
            {
                typeName = "timestamp";
            }
            else if (validator.indexOf("UTF8") >= 0)
            {
                typeName = "varchar";
            }
            // TODO Include other types
            String colName = row.getString("column_name");
            ColumnDetails col = new ColumnDetails(colName, row.getString("index_name"), typeName);
            cols.put(colName, col);
        }
        return cols;
    }

    public class ColumnDetails
    {
        String name;
        String indexName;
        String typeName;
        public ColumnDetails(String name, String idxName, String typeName)
        {
            this.name = name;
            this.indexName = idxName;
            this.typeName = typeName;
        }
    }

    private ColumnDetails getColumnDetailsForColumn(Column col, Map tableStructure)
    {
        String colName = col.getIdentifier();
        if (colName.startsWith("\""))
        {
            // Remove any quotes if the identifier is quoted since table structure will not have quotes
            colName = colName.substring(1, colName.length()-1);
        }
        return tableStructure.get(colName);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy