Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Instructions

  1. Create a rule called CheckProjectSchema.drl with the following content. Rule is also included below in the Download Rule section. Please note that there are different versions based on your Liquibase Enterprise version.

    1. Liquibase Enterprise Version 8.7 or higher

      Code Block
      // Copyright (c) 2024 Datical, Inc. All Rights Reserved.
      
      /*
      @author Liquibase
      @version 1.1
      @date May 24, 2024
      @description ERROR if SQL script references a schema outside Project file
      */
      
      /*
      README:
      
      This is a SQL Rule which throws ERROR when a sql script references a schema outside Project file.
      However dbDef under verification should have access to the schema used in the script. If it does not have access, error will NOT be thrown.
      To Execute this rule use:
      hammer runRules [dbDef] [SQL file | SQL files folder]
      
      */
      
      package com.datical.hammer.core.sqlrules.CheckProjectSchemas
      
      
      import com.datical.db.project.Project;
      import com.datical.db.project.Schema;
      import com.datical.db.project.DatabaseDef;
      import com.datical.dbsim.model.DbModel;
      import com.datical.db.project.Plan;
      import java.util.regex.*;
      import com.datical.db.project.util.ProjectUtil;
      import java.util.*;
      import com.google.common.collect.Iterables;
      import liquibase.database.Database;
      import liquibase.database.jvm.JdbcConnection;
      import com.datical.hammer.core.liquibase.status.LiquibaseAPIUtils;
      import com.datical.hammer.core.connectionservice.DatabaseBuilder;
      import java.sql.Connection;
      import com.datical.hammer.core.rules.ProjectResources;
      import com.datical.hammer.core.rules.Response;
      import com.datical.hammer.core.rules.Response.ResponseType;
      import com.datical.hammer.core.rules.WithComments;
      import com.datical.hammer.core.rules.WithoutComments;
      import com.datical.hammer.core.DatabaseDefUtil;
      import com.datical.hammer.core.extensions.DBOperations;
      
      
      /***********************************************************  Rules  *************************************************************/
      
      /* @return false if validation fails; true otherwise */
      function boolean validateSql(String sql, Project project, DBOperations operations, DatabaseDef dbDef, String regex) {
      
      	//System.out.println("Validation called: CheckProjectSchemas");
      	List<String> availableSchemas = getAvailableSchemas(project, operations, dbDef);
      	List<String> availableSchemasList =  new ArrayList<>();
      
      	for(String schema : availableSchemas) {
      		availableSchemasList.add(schema.toLowerCase());
      	}
      	boolean nonManagedSchemaFound = false;
          List<String> schemaStringList = new ArrayList<>();
          for (Schema schema : project.getSchemas()) {
            schemaStringList.add(schema.getName().toLowerCase().replaceAll("\\$\\{.*\\}.",""));
          }
      	//System.out.println("Available schemas size:"+availableSchemasList.size());
      	//System.out.println("Assigned schemas size:"+schemaStringList.size());
      
      	Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
      	Matcher matcher = pattern.matcher(sql);
      	while(matcher.find()){
      	String formattedSchemaName = matcher.group(1).replace("[","").replace("]","");
      
      	//System.out.println("Formatted Schema Name = " + formattedSchemaName);
      		if(!schemaStringList.contains(formattedSchemaName) && availableSchemasList.contains(formattedSchemaName)) {
      			nonManagedSchemaFound = true;
      			//System.out.println("Non Managed Schema Name found in script: " + formattedSchemaName);
      			break;
      		}
      	}
      
      	return nonManagedSchemaFound;
      }
      
      function List<String> getAvailableSchemas(Project project, DBOperations operations, DatabaseDef dbDef){
      	List<String> schemas =  new ArrayList();
      	Connection connection = null;
      	try {
      		connection = ((JdbcConnection) DatabaseBuilder.buildDatabase(dbDef).getConnection()).getWrappedConnection();
      		schemas = operations.getSchemaNames(connection);
      	 } catch (Exception dbe) {
      
      	 }  finally {
      		  if (connection != null){
      			try {
      			  connection.close();
      			} catch (Exception e) {
      			  ; // do nothing
      			}
      		  }
      		}
      	  return schemas;
      }
      
      rule "CheckProjectSchemas"
          salience 1
      	when
      	    $project : Project()
      		$operations : DBOperations()
      		$dbDef : DatabaseDef()
      		woc : WithoutComments(validateSql(getText().toLowerCase(), $project, $operations, $dbDef, "(\\S+)\\s*\\.\\s*(\\S+)"))
      	then
      		String errorMessage = "Script references a Schema not managed by this Liquibase pipeline.";
              insert(new Response(ResponseType.FAIL, errorMessage, drools.getRule().getName()));
      	end
    2. Liquibase Enterprise Version 7.6 to 8.6

      Code Block
      /*
      @author Liquibase
      @version 1.0
      @date July 28, 2020
      @description ERROR if SQL script references a schema outside Project file
      */
      
      /*
      README: 
      
      This is a SQL Rule which throws ERROR when a sql script references a schema outside Project file
      To Execute this rule use:
      hammer runRules [dbDef] [SQL file | SQL files folder]
      
      */
      
      package com.datical.hammer.core.sqlrules.CheckProjectSchemas
      
      
      import com.datical.db.project.Project;
      import com.datical.db.project.Schema;
      import com.datical.db.project.DatabaseDef;
      import com.datical.dbsim.model.DbModel;
      import com.datical.db.project.Plan;
      import java.util.regex.*;
      import com.datical.db.project.util.ProjectUtil;
      import java.util.*;
      import com.google.common.collect.Iterables;
      import liquibase.database.Database;
      import com.datical.hammer.core.liquibase.status.LiquibaseAPIUtils;
      import com.datical.hammer.core.connectionservice.DatabaseBuilder;
      import java.sql.Connection;
      import com.datical.hammer.core.rules.ProjectResources;
      import com.datical.hammer.core.rules.Response;
      import com.datical.hammer.core.rules.Response.ResponseType;
      import com.datical.hammer.core.rules.WithComments;
      import com.datical.hammer.core.rules.WithoutComments;
      import com.datical.hammer.core.DatabaseDefUtil;
      import com.datical.hammer.core.extensions.DBOperations;
      
      
      /***********************************************************  Rules  *************************************************************/
      
      /* @return false if validation fails; true otherwise */
      function boolean validateSql(String sql, Project project, DBOperations operations, String regex) {
          
      	System.out.println("Validation called: CheckProjectSchemas");
      	List<String> availableSchemas = getAvailableSchemas(project, operations);
      	List<String> availableSchemasList =  new ArrayList<>();
      
      	for(String schema : availableSchemas) {
      		availableSchemasList.add(schema.toLowerCase());
      	}
      	boolean nonManagedSchemaFound = false;
          List<String> schemaStringList = new ArrayList<>();
          for (Schema schema : project.getSchemas()) {
            schemaStringList.add(schema.getName().toLowerCase().replaceAll("\\$\\{.*\\}.",""));
          }
      	//System.out.println("Available schemas size:"+availableSchemasList.size());
      	//System.out.println("Assigned schemas size:"+schemaStringList.size());
      
      	Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
      	Matcher matcher = pattern.matcher(sql);
      	while(matcher.find()){
      	String formattedSchemaName = matcher.group(1).replace("[","").replace("]","");
      	
      	//System.out.println("Formatted Schema Name = " + formattedSchemaName);
      		if(!schemaStringList.contains(formattedSchemaName) && availableSchemasList.contains(formattedSchemaName)) {
      			nonManagedSchemaFound = true;
      			System.out.println("Non Managed Schema Name found in script: " + formattedSchemaName);
      			break;
      		}
      	}
      	
      	return nonManagedSchemaFound;
      }
      
      function DatabaseDef getDatabaseDef(Project project) {
      	String dbName = project.getSchemaSelectionStep();
      	DatabaseDef dbDef = DatabaseDefUtil.findDbByName(project, dbName);
      	return dbDef;
      }
      
      function List<String> getAvailableSchemas(Project project, DBOperations operations){
      	DatabaseDef dbDef = getDatabaseDef(project);
      	List<String> schemas =  new ArrayList();
      	Connection connection = null;
      	try {
      		connection = DatabaseBuilder.buildPlainConnection(dbDef);
      		schemas = operations.getSchemaNames(connection);		
      	 } catch (Exception dbe) {
      			
      	 }  finally {
      		  if (connection != null){
      			try {
      			  connection.close();
      			} catch (Exception e) {
      			  ; // do nothing
      			}
      		  }
      		}
      	  return schemas;
      }
      
      rule "Check Project Schemas Format"
          salience 1
      	when
      	    $project : Project()
      		$operations : DBOperations()
      		woc : WithoutComments(validateSql(getText().toLowerCase(), $project, $operations, "(\\S+)\\s*\\.\\s*(\\S+)"))
      	then
      		String errorMessage = "Script references a Schema not listed in Project file (Sql Script: " + woc.getSqlFile().getName() + ")";
              insert(new Response(ResponseType.FAIL, errorMessage, drools.getRule().getName()));
      	end
  2. This rule needs to be placed in <project_dir>/Rules/SqlRules

  3. This rule should account for schemas referenced in any of the below formats when using the actual schema name (not property substitution for schema name). Additional schema patterns can be included by modifying the regex pattern for object names:

    1. schema_name.object_name

    2. [schema_name].[object_name]

    3. schema_name.[object_name]

    4. [schema_name].object_name

  4. There may be matches for the regex pattern in the sql script that are not schema names. In order to avoid triggering the rule in these false positive cases, the rule compares the schema name against the list of available schemas on the database. If the schema name is not included in the list of available schemas the match will be disregarded.

  5. Example Packager Error:

  6. If you wish to check the rule using hammer runRules, a dbDef parameter must be included. This command needs to be run from the ddb repo:

    Code Block
    hammer runRules [dbDef] [SQL file | SQL files folder]

...