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

tbrugz.sqldiff.SQLDiff Maven / Gradle / Ivy

There is a newer version: 0.9.17
Show newest version
package tbrugz.sqldiff;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import javax.naming.NamingException;
import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLStreamException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tbrugz.sqldiff.datadiff.DataDiff;
import tbrugz.sqldiff.model.DBIdentifiableDiff;
import tbrugz.sqldiff.model.Diff;
import tbrugz.sqldiff.model.SchemaDiff;
import tbrugz.sqldiff.model.ColumnDiff;
import tbrugz.sqldiff.validate.DiffValidator;
import tbrugz.sqldump.SchemaModelScriptDumper;
import tbrugz.sqldump.dbmd.DBMSFeatures;
import tbrugz.sqldump.dbmodel.DBObject;
import tbrugz.sqldump.dbmodel.DBObjectType;
import tbrugz.sqldump.dbmodel.SchemaModel;
import tbrugz.sqldump.def.DBMSResources;
import tbrugz.sqldump.def.Defs;
import tbrugz.sqldump.def.Executor;
import tbrugz.sqldump.def.ProcessingException;
import tbrugz.sqldump.def.SchemaModelGrabber;
import tbrugz.sqldump.processors.DirectoryCleaner;
import tbrugz.sqldump.util.CLIProcessor;
import tbrugz.sqldump.util.CategorizedOut;
import tbrugz.sqldump.util.ConnectionUtil;
import tbrugz.sqldump.util.ParametrizedProperties;
import tbrugz.sqldump.util.SQLIdentifierDecorator;
import tbrugz.sqldump.util.Utils;

/*
 * XXX: output diff by change type
 * 
 * XXX: option: [ignore|do not ignore] case; ignore schema name
 */
public class SQLDiff implements Executor {
	
	public static final String PROPERTIES_FILENAME = "sqldiff.properties";
	public static final String PRODUCT_NAME = "sqldiff";

	public static final String ID_SOURCE = "source";
	public static final String ID_TARGET = "target";
	
	//base props
	public static final String PROP_PREFIX = "sqldiff";
	public static final String PROP_SOURCE = PROP_PREFIX+"."+ID_SOURCE;
	public static final String PROP_TARGET = PROP_PREFIX+"."+ID_TARGET;
	public static final String PROP_TYPES_TO_DIFF = PROP_PREFIX+".typestodiff";
	
	//schema input/output props
	static final String PREFIX_INPUT = PROP_PREFIX+".input";
	static final String PREFIX_OUTPUT = PROP_PREFIX+".output";
	public static final String PROP_XMLINFILE = PREFIX_INPUT+".xmlfile";
	public static final String PROP_XMLOUTFILE = PREFIX_OUTPUT+".xmlfile";
	public static final String PROP_JSONINFILE = PREFIX_INPUT+".jsonfile";
	public static final String PROP_JSONOUTFILE = PREFIX_OUTPUT+".jsonfile";
	public static final String PROP_OUTFILEPATTERN = PROP_PREFIX+".outfilepattern"; //XXX: rename to 'sqldiff.output.filepattern'?
	//patch-output
	public static final String PREFIX_PATCH = PREFIX_OUTPUT+".patch";
	public static final String PROP_PATCHFILEPATTERN = PREFIX_PATCH+".file";
	
	//other props
	public static final String PROP_DO_DATADIFF = PROP_PREFIX+".dodatadiff";
	public static final String PROP_FAILONERROR = PROP_PREFIX+".failonerror";
	public static final String PROP_DELETEREGULARFILESDIR = PROP_PREFIX+".deleteregularfilesfromdir";
	public static final String PROP_ADD_COMMENTS = PROP_PREFIX+".addcomments";
	
	//type-dependent props
	public static final String PROP_COLUMNDIFF_TEMPCOLSTRATEGY = PROP_PREFIX+".columndiff.tempcolstrategy";
	public static final String PROP_DBIDDIFF_USEREPLACE = PROP_PREFIX+".dbiddiff.usereplace";

	//rename detection
	public static final String PROP_DO_RENAMEDETECTION = PROP_PREFIX+".dorenamedetection";
	public static final String PROP_RENAMEDETECT_MINSIMILARITY = PROP_PREFIX+".renamedetection.minsimilarity";
	public static final String PROP_RENAMEDETECT_TYPES = PROP_PREFIX+".renamedetection.types";

	//apply diff props
	static final String PROP_DO_APPLYDIFF = PROP_PREFIX+".doapplydiff";
	static final String PROP_APPLYDIFF_TOSOURCE = PROP_PREFIX+".applydiff.tosource";
	static final String PROP_APPLYDIFF_TOCONN = PROP_PREFIX+".applydiff.toconn";
	static final String PROP_APPLYDIFF_TOID = PROP_PREFIX+".applydiff.toid";
	static final String PROP_APPLYDIFF_VALIDATE = PROP_PREFIX+".applydiff.validate";
	static final String PROP_APPLYDIFF_SCHEMADIFF = PROP_PREFIX+".doapplyschemadiff";
	static final String PROP_APPLYDIFF_DATADIFF = PROP_PREFIX+".doapplydatadiff";
	
	public static final boolean DEFAULT_DO_RENAME_DETECTION = false; //XXX: should be true?
	
	//props from SchemaModelScriptDumper
	@Deprecated static final String FILENAME_PATTERN_SCHEMA = "${schemaname}";
	@Deprecated static final String FILENAME_PATTERN_OBJECTTYPE = "${objecttype}";
	
	static final double RENAMEDETECT_MINSIMILARITY_DEFAULT = 0.5;
	
	static final String XML_IO_CLASS = "tbrugz.sqldiff.io.XMLDiffIO";
	static final String JSON_IO_CLASS = "tbrugz.sqldiff.io.JSONDiffIO";
	static final String PATCH_DUMPER_CLASS = "tbrugz.sqldiff.patch.PatchDumper";

	static final Log log = LogFactory.getLog(SQLDiff.class);
	
	final Properties prop = new ParametrizedProperties();

	boolean failonerror = true;
	String outfilePattern = null;
	
	String xmlinfile = null;
	String xmloutfile = null;

	String jsoninfile = null;
	String jsonoutfile = null;

	String patchfilePattern = null;
	
	// writers
	CategorizedOut categOut = null; 
	Writer xmlWriter = null;
	Writer jsonWriter = null;
	Writer patchWriter = null;
	
	transient int lastDiffCount = 0;
	
	class ModelGrabber implements Callable {
		
		final SchemaModelGrabber schemaGrabber;
		final String grabberMode;
		final String grabberId;
		
		public ModelGrabber(SchemaModelGrabber grabber, String grabberMode, String grabberId) {
			this.grabberMode = grabberMode;
			this.grabberId = grabberId;
			this.schemaGrabber = grabber;
		}
		
		/*public ModelGrabber(String grabberMode, String grabberId) throws ClassNotFoundException, SQLException, NamingException {
			this.grabberMode = grabberMode;
			this.grabberId = grabberId;
			this.schemaGrabber = initGrabber(grabberMode, grabberId, prop);
		}*/
		
		@Override
		public SchemaModel call() throws Exception {
			try {
				//get grabber
				//this.schemaGrabber = initGrabber(grabberMode, grabberId, prop);
				
				//grab schemas
				log.info("grabbing '"+grabberMode+"' model ["+grabberId+"]");
				return schemaGrabber.grabSchema();
			}
			catch(Exception e) {
				log.warn("ModelGrabber:: call[grabSchema]: "+e, e);
				throw e;
			}
		}
	}
	
	public int doIt() throws ClassNotFoundException, SQLException, NamingException, IOException, JAXBException, XMLStreamException, InterruptedException, ExecutionException {
		
		SchemaModelGrabber fromSchemaGrabber = null;
		SchemaModelGrabber toSchemaGrabber = null;
		SchemaModel fromSM = null;
		SchemaModel toSM = null;
		String sourceId = null;
		String targetId = null;
		
		SchemaDiff diff = null;
		
		String diffDialect = DBMSResources.DEFAULT_DBID;
		
		long initTime = System.currentTimeMillis();
		
		if(xmlinfile!=null) {
			DiffGrabber dg = (DiffGrabber) Utils.getClassInstance(XML_IO_CLASS);
			diff = (SchemaDiff) dg.grabDiff(new File(xmlinfile));
			if(diff.getSqlDialect()!=null) { diffDialect = diff.getSqlDialect(); }
			setupFeatures(diffDialect);
		}
		else if(jsoninfile!=null) {
			DiffGrabber dg = (DiffGrabber) Utils.getClassInstance(JSON_IO_CLASS);
			diff = (SchemaDiff) dg.grabDiff(new File(jsoninfile));
			if(diff.getSqlDialect()!=null) { diffDialect = diff.getSqlDialect(); }
			setupFeatures(diffDialect);
		}
		else {
			//from
			sourceId = prop.getProperty(PROP_SOURCE);
			fromSchemaGrabber = initGrabber(ID_SOURCE, sourceId, prop);
			
			//to
			targetId = prop.getProperty(PROP_TARGET);
			toSchemaGrabber = initGrabber(ID_TARGET, targetId, prop);
			
			//XXX: add allowParallel property?
			// grab schemas - parallel execution
			ModelGrabber sourceGrabber = new ModelGrabber(fromSchemaGrabber, ID_SOURCE, sourceId);
			ModelGrabber targetGrabber = new ModelGrabber(toSchemaGrabber, ID_TARGET, targetId);
			
			ExecutorService executor = Executors.newFixedThreadPool(2);
			
			Future futureSourceSM = executor.submit(sourceGrabber);
			Future futureTargetSM = executor.submit(targetGrabber);
			executor.shutdown();
			
			fromSM = futureSourceSM.get(); //blocks for return
			toSM = futureTargetSM.get();
			
			/*// grab schemas - sequential
			log.info("grabbing 'source' model ["+sourceId+"]");
			fromSM = fromSchemaGrabber.grabSchema();
			log.info("grabbing 'target' model ["+targetId+"]");
			toSM = toSchemaGrabber.grabSchema();
			*/
			
			diff = doDiffSchemas(fromSM, toSM);
			diffDialect = diff.getSqlDialect();
			doDetectRenames(diff); //XXX: should detect renames even when using XML_IO_CLASS or JSON_IO_CLASS ?
		}
		
		//delete files from dir...
		String dirToDeleteFiles = prop.getProperty(PROP_DELETEREGULARFILESDIR);
		if(dirToDeleteFiles!=null) {
			DirectoryCleaner dc = new DirectoryCleaner();
			dc.setDirToDeleteFiles(new File(dirToDeleteFiles));
			dc.process();
		}

		//dump diff
		/*if(outfilePattern!=null) {
			String finalPattern = CategorizedOut.generateFinalOutPattern(outfilePattern, 
					new String[]{FILENAME_PATTERN_SCHEMA, Defs.addSquareBraquets(Defs.PATTERN_SCHEMANAME)},
					new String[]{FILENAME_PATTERN_OBJECTTYPE, Defs.addSquareBraquets(Defs.PATTERN_OBJECTTYPE)},
					new String[]{Defs.addSquareBraquets(Defs.PATTERN_OBJECTNAME)},
					new String[]{Defs.addSquareBraquets(Defs.PATTERN_CHANGETYPE)}
					);
			CategorizedOut co = new CategorizedOut(finalPattern);
			log.debug("final pattern: "+finalPattern);
			
			//co.categorizedOut(diff.getDiff());
			//log.info("dumping diff...");
			diff.outDiffs(co);
		}*/
		//dump diff
		if(categOut!=null) {
			diff.outDiffs(categOut);
		}
		
		if(xmlWriter!=null) {
			try {
				DiffDumper dd = (DiffDumper) Utils.getClassInstance(XML_IO_CLASS);
				dd.setProperties(prop);
				dd.dumpDiff(diff, xmlWriter);
			} catch (JAXBException e) {
				log.warn("error writing xml: "+e);
				log.debug("error writing xml: "+e.getMessage(),e);
			}
		}

		if(jsonWriter!=null) {
			try {
				DiffDumper dd = (DiffDumper) Utils.getClassInstance(JSON_IO_CLASS);
				dd.setProperties(prop);
				dd.dumpDiff(diff, jsonWriter);
			} catch (JAXBException e) {
				log.warn("error writing json: "+e);
				log.debug("error writing json: "+e.getMessage(),e);
			}
		}
		
		//out patch - show changed lines, ... ; dbiddiff: replace changetype
		if(patchWriter!=null) {
			if(!SchemaDiffer.mayReplaceDbId) {
				log.warn("using PatchDumper with '"+PROP_DBIDDIFF_USEREPLACE+"'=="+SchemaDiffer.mayReplaceDbId+": duplicate diffs may appear");
			}
			
			DiffDumper dd = (DiffDumper) Utils.getClassInstance(PATCH_DUMPER_CLASS);
			dd.setProperties(prop);
			dd.dumpDiff(diff, patchWriter);
		}

		boolean doDataDiff = Utils.getPropBool(prop, PROP_DO_DATADIFF, false);
		boolean doApplyDiff = Utils.getPropBool(prop, PROP_DO_APPLYDIFF, false);
		boolean doApplySchemaDiff = doApplyDiff && Utils.getPropBool(prop, PROP_APPLYDIFF_SCHEMADIFF, false);
		boolean doApplyDataDiff = doApplyDiff && Utils.getPropBool(prop, PROP_APPLYDIFF_DATADIFF, false);
		boolean doApplyValidate = Utils.getPropBool(prop, PROP_APPLYDIFF_VALIDATE, true);
		
		boolean applyToSource = Utils.getPropBool(prop, PROP_APPLYDIFF_TOSOURCE, false);
		String applyToId = prop.getProperty(PROP_APPLYDIFF_TOID);
		String applyToConnPrefix = prop.getProperty(PROP_APPLYDIFF_TOCONN);
		
		if(doApplyDiff && !doApplySchemaDiff && !doApplyDataDiff) {
			String message = "apply diff prop defined, but no schemadiff ('"+PROP_APPLYDIFF_SCHEMADIFF+"') nor datadiff ('"+PROP_APPLYDIFF_DATADIFF+"') properties defined";
			log.warn(message);
			if(failonerror) { throw new ProcessingException(message); }
		}
		
		//apply schema diff to database?
		if(doApplySchemaDiff) {
			boolean connectionCreated = false;
			Connection applyToConn = null;
			SchemaModel applyToModel = null;
			
			if(applyToSource) {
				if(fromSchemaGrabber!=null) {
					applyToConn = fromSchemaGrabber.getConnection();
					applyToModel = fromSM;
				}
				if(applyToConn==null) {
					//if source was not grabbed from JDBC/connection
					String connPrefix = "sqldiff."+prop.getProperty(PROP_SOURCE);
					log.info("initting 'source' connection to apply diff [prefix = '"+connPrefix+"']");
					applyToConn = ConnectionUtil.initDBConnection(connPrefix, prop);
					connectionCreated = true;
				}
			}
			else if(applyToId!=null) {
				if(applyToId.equals(sourceId)) {
					applyToModel = fromSM;
				}
				else if(applyToId.equals(targetId)) {
					applyToModel = toSM;
				}
				else {
					SchemaModelGrabber applyToGrabber = initGrabber("apply-to", applyToId, prop);
					log.info("grabbing 'apply-to' model ["+applyToId+"]");
					applyToModel = applyToGrabber.grabSchema();
					applyToConn = applyToGrabber.getConnection();
				}
				if(applyToConn==null) {
					String connPrefix = "sqldiff."+applyToId;
					log.info("initting 'apply-to' connection to apply diff [prefix = '"+connPrefix+"']");
					applyToConn = ConnectionUtil.initDBConnection(connPrefix, prop);
					connectionCreated = true;
				}
			}
			else if(applyToConnPrefix!=null) {
				log.info("initting connection to apply diff [prefix = '"+applyToConnPrefix+"']");
				applyToConn = ConnectionUtil.initDBConnection(applyToConnPrefix, prop);
				connectionCreated = true;
			}
			else {
				String message = "applydiff (ditt-to-db) target (prop '"+PROP_APPLYDIFF_TOSOURCE+"' or '"+PROP_APPLYDIFF_TOCONN+"') not defined";
				log.warn(message);
				if(failonerror) { throw new ProcessingException(message); }
			}
			
			applySchemaDiff(diff, applyToModel, applyToConn, doApplyValidate);
			
			if(connectionCreated) { ConnectionUtil.closeConnection(applyToConn); }
		}

		//data diff!
		if(doDataDiff) {
			DataDiff dd = new DataDiff();
			dd.setFailOnError(failonerror);
			//XXX: some refactoring would be nice...
			if(applyToSource) {
				String sourceGrabberId = prop.getProperty(PROP_SOURCE);
				prop.setProperty("sqldiff.datadiff.sdd2db.connpropprefix", "sqldiff."+sourceGrabberId);
			}
			dd.setProperties(prop);
			dd.setApplyDataDiff(doApplyDataDiff);
			dd.setSourceSchemaModel(fromSM);
			if(fromSchemaGrabber!=null) {
				dd.setSourceConnection(fromSchemaGrabber.getConnection());
			}
			dd.setTargetSchemaModel(toSM);
			if(toSchemaGrabber!=null) {
				dd.setTargetConnection(toSchemaGrabber.getConnection());
			}
			dd.process();
		}
		
		if(fromSchemaGrabber!=null) {
			ConnectionUtil.closeConnection(fromSchemaGrabber.getConnection());
		}
		if(toSchemaGrabber!=null) {
			ConnectionUtil.closeConnection(toSchemaGrabber.getConnection());
		}
		
		log.info("...done [elapsed="+(System.currentTimeMillis()-initTime)+"ms]");
		
		return diff.getDiffList().size();
	}

	void setupFeatures(String dialect) {
		log.debug("diff dialect set to: "+dialect);
		//DBMSResources.instance().updateDbId(dialect);
		DBMSFeatures feat = DBMSResources.instance().getSpecificFeatures(dialect);
		ColumnDiff.updateFeatures(feat);
	}
	
	SchemaDiff doDiffSchemas(SchemaModel fromSM, SchemaModel toSM) {
		//XXX: option to set dialect from properties?
		String dialect = toSM.getSqlDialect();
		setupFeatures(dialect);
		
		//do diff
		log.info("diffing...");
		SchemaDiffer differ = new SchemaDiffer();
		differ.setTypesForDiff(prop.getProperty(PROP_TYPES_TO_DIFF));
		return differ.diffSchemas(fromSM, toSM);
	}
	
	void doDetectRenames(SchemaDiff diff) {
		//detect renames
		//XXX: add DiffProcessor?
		//XXX: add prop 'sqldiff.renamedetection.types'?
		boolean doRenameDetection = Utils.getPropBool(prop, PROP_DO_RENAMEDETECTION, DEFAULT_DO_RENAME_DETECTION);
		
		if(doRenameDetection) {
			double minSimilarity = Utils.getPropDouble(prop, PROP_RENAMEDETECT_MINSIMILARITY, RENAMEDETECT_MINSIMILARITY_DEFAULT);
			int renames = 0;
			List renameTypes = Utils.getStringListFromProp(prop, PROP_RENAMEDETECT_TYPES, ",", RenameDetector.RENAME_TYPES); // DBObjectType.values() ?
			
			if(renameTypes!=null) {
				log.info("types to detect renames: "+renameTypes);
			}
			
			if(renameTypes==null || renameTypes.contains(DBObjectType.TABLE.toString())) {
				renames += RenameDetector.detectAndDoTableRenames(diff.getTableDiffs(), minSimilarity);
			}
			if(renameTypes==null || renameTypes.contains(DBObjectType.COLUMN.toString())) {
				renames += RenameDetector.detectAndDoColumnRenames(diff.getColumnDiffs(), minSimilarity);
			}
			if(renameTypes==null || renameTypes.contains(DBObjectType.INDEX.toString())) {
				renames += RenameDetector.detectAndDoIndexRenames(diff.getDbIdDiffs(), minSimilarity);
			}
			if(renameTypes==null || renameTypes.contains(DBObjectType.CONSTRAINT.toString())) {
				renames += RenameDetector.detectAndDoConstraintRenames(diff.getDbIdDiffs(), minSimilarity);
			}
			//XXX detect FK renames?
			if(renames>0) {
				SchemaDiff.logInfo(diff);
			}
		}
	}

	void applySchemaDiff(SchemaDiff diff, SchemaModel applyToModel, Connection applyToConn, boolean doApplyValidate) throws SQLException {
		if(applyToConn==null) {
			String message = "applySchemaDiff: connection is null!";
			log.warn(message);
			if(failonerror) { throw new ProcessingException(message); }
		}
		else {
			if(applyToModel!=null) {
				if(doApplyValidate) {
					DiffValidator dv = new DiffValidator(applyToModel);
					dv.validateDiff(diff);
				}
			}
			else {
				log.info("no 'apply-to' model defined, diff may not be validated");
			}
			//DBMSResources.instance().updateMetaData(applyToConn.getMetaData());
			applyDiffToDB(diff, applyToConn);
		}
	}
	
	static SchemaModelGrabber initSchemaModelGrabberInstance(String grabClassName) {
		SchemaModelGrabber schemaGrabber = null;
		if(grabClassName!=null) {
			schemaGrabber = (SchemaModelGrabber) Utils.getClassInstance(grabClassName, Defs.DEFAULT_CLASSLOADING_PACKAGES);
			if(schemaGrabber==null) {
				log.warn("schema grabber class '"+grabClassName+"' not found");
			}
		}
		else {
			log.warn("null grab class name!");
		}
		return schemaGrabber;
	}
	
	static SchemaModelGrabber initGrabber(String grabberLabel, String grabberId, Properties prop) throws ClassNotFoundException, SQLException, NamingException {
		if(grabberId==null || "".equals(grabberId)) {
			throw new ProcessingException("'"+grabberLabel+"' grabber id not defined");
		}
		
		log.info(grabberLabel+" model ["+grabberId+"] init");
		//String connPropPrefix = "sqldiff."+grabberId;
		String connPropPrefix = prop.getProperty("sqldiff."+grabberId+".connpropprefix", "sqldiff."+grabberId);
		
		String grabClassName = prop.getProperty(connPropPrefix+".grabclass");
		if(grabClassName==null) {
			throw new ProcessingException("'"+grabberLabel+"' grabber class not defined [id="+grabberId+" ; prefix="+connPropPrefix+"]");
		}
		
		SchemaModelGrabber schemaGrabber = initSchemaModelGrabberInstance(grabClassName);
		schemaGrabber.setId(grabberId);
		schemaGrabber.setPropertiesPrefix(connPropPrefix);
		if(schemaGrabber.needsConnection()) {
			Connection conn = ConnectionUtil.initDBConnection(connPropPrefix, prop);
			schemaGrabber.setConnection(conn);
		}
		schemaGrabber.setProperties(prop);
		return schemaGrabber;
	}
	
	@SuppressWarnings("deprecation")
	public void procProterties() {
		failonerror = Utils.getPropBool(prop, PROP_FAILONERROR, failonerror);
		DBObject.dumpCreateOrReplace = Utils.getPropBool(prop, SchemaModelScriptDumper.PROP_SCHEMADUMP_USECREATEORREPLACE, false);
		SQLIdentifierDecorator.dumpQuoteAll = Utils.getPropBool(prop, SchemaModelScriptDumper.PROP_SCHEMADUMP_QUOTEALLSQLIDENTIFIERS, SQLIdentifierDecorator.dumpQuoteAll);
		
		outfilePattern = prop.getProperty(PROP_OUTFILEPATTERN);
		xmlinfile = prop.getProperty(PROP_XMLINFILE);
		xmloutfile = prop.getProperty(PROP_XMLOUTFILE);
		jsoninfile = prop.getProperty(PROP_JSONINFILE);
		jsonoutfile = prop.getProperty(PROP_JSONOUTFILE);
		patchfilePattern = prop.getProperty(PROP_PATCHFILEPATTERN);
		
		String colDiffTempStrategy = prop.getProperty(PROP_COLUMNDIFF_TEMPCOLSTRATEGY);
		if(colDiffTempStrategy!=null) {
			try {
				ColumnDiff.useTempColumnStrategy = ColumnDiff.TempColumnAlterStrategy.valueOf(colDiffTempStrategy.toUpperCase());
			}
			catch(IllegalArgumentException e) {
				String message = "illegal value '"+colDiffTempStrategy+"' to prop '"+PROP_COLUMNDIFF_TEMPCOLSTRATEGY+"' [default is '"+ColumnDiff.useTempColumnStrategy+"']";
				log.warn(message);
				if(failonerror) {
					throw new ProcessingException(message, e);
				}
			}
		}
		boolean addComments = Utils.getPropBool(prop, PROP_ADD_COMMENTS, true);
		ColumnDiff.addComments = addComments;
		DBIdentifiableDiff.addComments = addComments;
		SchemaDiffer.mayReplaceDbId = Utils.getPropBool(prop, PROP_DBIDDIFF_USEREPLACE, SchemaDiffer.mayReplaceDbId);
		DBMSResources.instance().setup(prop);
	}
	
	void openFileWriters() throws IOException {
		if(outfilePattern!=null) {
			String finalPattern = CategorizedOut.generateFinalOutPattern(outfilePattern, 
					new String[]{FILENAME_PATTERN_SCHEMA, Defs.addSquareBraquets(Defs.PATTERN_SCHEMANAME)},
					new String[]{FILENAME_PATTERN_OBJECTTYPE, Defs.addSquareBraquets(Defs.PATTERN_OBJECTTYPE)},
					new String[]{Defs.addSquareBraquets(Defs.PATTERN_OBJECTNAME)},
					new String[]{Defs.addSquareBraquets(Defs.PATTERN_CHANGETYPE)}
					);
			CategorizedOut co = new CategorizedOut(finalPattern);
			setCategorizedOut(co);
			log.debug("final pattern: "+finalPattern);
		}
		
		if(xmloutfile!=null) {
			File f = new File(xmloutfile);
			Utils.prepareDir(f);
			xmlWriter = new FileWriter(f);
		}
		if(jsonoutfile!=null) {
			File f = new File(jsonoutfile);
			Utils.prepareDir(f);
			jsonWriter = new FileWriter(f);
		}
		if(patchfilePattern!=null) {
			File f = new File(patchfilePattern);
			Utils.prepareDir(f);
			patchWriter = new FileWriter(f);
		}
	}
	
	void applyDiffToDB(SchemaDiff diff, Connection conn) throws SQLException {
		//do not apply DDLs with comments
		boolean savedAddComments = ColumnDiff.addComments;
		ColumnDiff.addComments = false;
		
		List diffs = diff.getChildren();

		int diffCount = 0;
		int execCount = 0;
		int errorCount = 0;
		int updateCount = 0;
		int skipCount = 0;
		
		SQLException lastEx = null;
		for(Diff d: diffs) {
			diffCount++;
			//sqldiff.applydiff.DROP=COLUMN, TABLE, ...
			List types = Utils.getStringListFromProp(prop, "sqldiff.applydiff."+d.getChangeType(), ",");
			if(types!=null && !types.contains(d.getObjectType().toString())) {
				log.info("diff #"+diffCount+": '"+d.getNamedObject()+"'s type ["+d.getObjectType()+"] not in '"+d.getChangeType()+"' types: "+types);
				skipCount++;
				continue;
			}
			
			try {
				//XXX: option to send all SQLs from one diff in only one statement? no problem for h2...  
				List sqls = d.getDiffList();
				for(int i=0;i1?" ["+(i+1)+"/"+sqls.size()+"]: ":" ")
						+("[ "+d.getChangeType()+": "+d.getNamedObject()+" ]: ")
						+sql);
					execCount++;
					updateCount += conn.createStatement().executeUpdate(sql);
				}
			} catch (SQLException e) {
				errorCount++;
				lastEx = e;
				log.warn("error executing diff: "+e);
				if(failonerror) { break; }
			}
		}
		log.info(
				(execCount>0?execCount:"no")
				+" diff statements executed"
				+(errorCount>0?" [#errors = "+errorCount+"]":"")
				+(updateCount>0?" [#updates = "+updateCount+"]":"")
				+(skipCount>0?" [#skiped = "+skipCount+"]":"")
				);

		ColumnDiff.addComments = savedAddComments;
		
		if(execCount>0 && errorCount==0) {
			log.info("committing "+execCount+" changes...");
			conn.commit();
		}
		
		if(failonerror && errorCount>0) {
			throw new ProcessingException(errorCount+" sqlExceptions occured", lastEx);
		}
	}

	@Override
	public void doMain(String[] args, Properties properties) throws ClassNotFoundException, SQLException, NamingException, IOException, JAXBException, XMLStreamException, InterruptedException, ExecutionException {
		if(properties!=null) {
			prop.putAll(properties);
		}
		if(CLIProcessor.shouldStopExec(PRODUCT_NAME, args)) {
			return;
		}
		CLIProcessor.init(PRODUCT_NAME, args, PROPERTIES_FILENAME, prop);
		
		procProterties();

		if(outfilePattern==null && xmloutfile==null) {
			String message = "outfilepattern [prop '"+PROP_OUTFILEPATTERN+"'] nor xmloutfile [prop '"+PROP_XMLOUTFILE+"'] nor jsonoutfile [prop '"+PROP_JSONOUTFILE+"'] defined. can't dump diff script";
			log.error(message);
			if(failonerror) { throw new ProcessingException(message); }
			return;
		}
		
		openFileWriters();
		
		lastDiffCount = doIt();
	}

	public static void main(String[] args) throws ClassNotFoundException, SQLException, NamingException, IOException, JAXBException, XMLStreamException, InterruptedException, ExecutionException {
		SQLDiff sqldiff = new SQLDiff();
		sqldiff.doMain(args, sqldiff.prop);
	}
	
	@Override
	public void setFailOnError(boolean failonerror) {
		this.failonerror = failonerror;
	}
	
	public int getLastDiffCount() {
		return lastDiffCount;
	}
	
	public void setProperties(Properties properties) {
		if(properties!=null) {
			prop.putAll(properties);
		}
	}
	
	public void setCategorizedOut(CategorizedOut out) {
		categOut = out;
	}
	
	public void setXmlWriter(Writer writer) {
		xmlWriter = writer;
	}

	public void setJsonWriter(Writer writer) {
		jsonWriter = writer;
	}

	public void setPatchWriter(Writer writer) {
		patchWriter = writer;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy