class SQLFileChange extends AbstractSQLChange {

    private String path;
    private Boolean relativeToChangelogFile;
    private Boolean doExpandExpressionsInGenerateChecksum = false;

    @Override
    public boolean generateStatementsVolatile(Database database) {
        return false;
    }

    @Override
    public boolean generateRollbackStatementsVolatile(Database database) {
        return false;
    }

    @DatabaseChangeProperty(description = "The file path of the SQL file to load",
            exampleValue = "my/path/file.sql", requiredForDatabase = "all")
    public String getPath() {
        return path;
    }

    /**
     * Sets the file name but setUp must be called for the change to have impact.
     *
     * @param fileName The file to use
     */
    public void setPath(String fileName) {
        path = fileName;
    }

    /**
     * The encoding of the file containing SQL statements
     *
     * @return the encoding
     */
    @DatabaseChangeProperty(exampleValue = "utf8", description = "Encoding used in the file you specify in 'path'")
    public String getEncoding() {
        return encoding;
    }

    /**
     * @param encoding the encoding to set
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    @DatabaseChangeProperty(description = "Specifies whether the file path is relative to the changelog file " +
        "rather than looked up in the search path. Default: false.")
    public Boolean isRelativeToChangelogFile() {
        return relativeToChangelogFile;
    }

    public void setRelativeToChangelogFile(Boolean relativeToChangelogFile) {
        this.relativeToChangelogFile = relativeToChangelogFile;
    }

    public void setDoExpandExpressionsInGenerateChecksum(Boolean doExpandExpressionsInGenerateChecksum) {
        this.doExpandExpressionsInGenerateChecksum = doExpandExpressionsInGenerateChecksum;
    }

    @Override
    public void finishInitialization() throws SetupException {
        if (path == null) {
            throw new SetupException("<sqlfile> - No path specified");
        }
    }

    @Override
    public InputStream openSqlStream() throws IOException {
        if (path == null) {
            return null;
        }

        return getResource().openInputStream();
    }

    private Resource getResource() throws IOException {
        ResourceAccessor resourceAccessor = Scope.getCurrentScope().getResourceAccessor();
        if (ObjectUtil.defaultIfNull(isRelativeToChangelogFile(), false)) {
            return resourceAccessor.get(getChangeSet().getChangeLog().getPhysicalFilePath()).resolveSibling(path);
        } else {
            return resourceAccessor.getExisting(path);
        }
    }

    @Override
    public ValidationErrors validate(Database database) {<FILL_FUNCTION_BODY>}

    private void alertOnNonExistantSqlFile(ValidationErrors validationErrors) {
        if (ChangeLogParserConfiguration.ON_MISSING_SQL_FILE.getCurrentValue().equals(ChangeLogParserConfiguration.MissingIncludeConfiguration.WARN)) {
            validationErrors.addWarning(FileUtil.getFileNotFoundMessage(path));
        } else {
            validationErrors.addError(FileUtil.getFileNotFoundMessage(path));
        }
    }

    @Override
    public String getConfirmationMessage() {
        return "SQL in file " + path + " executed";
    }

    @Override
    @DatabaseChangeProperty(isChangeProperty = false)
    public String getSql() {
        return getSql(true);
    }
 
    public String getSql(boolean doExpandExpressions) {
        String sql = super.getSql();
        if (sql == null) {
            try (InputStream sqlStream = openSqlStream()) {
                if (sqlStream == null) {
                    return null;
                }
                String content = StreamUtil.readStreamAsString(sqlStream, getEncoding());
                if (doExpandExpressions && getChangeSet() != null) {
                    ChangeLogParameters parameters = getChangeSet().getChangeLogParameters();
                    if (parameters != null) {
                        content = parameters.expandExpressions(content, getChangeSet().getChangeLog());
                    }
                }
                return content;
            } catch (IOException e) {
                throw new UnexpectedLiquibaseException(e);
            }
        } else {
            return sql;
        }
    }

    @Override
    public void setSql(String sql) {
        if ((getChangeSet() != null) && (getChangeSet().getChangeLogParameters() != null)) {
            sql = getChangeSet().getChangeLogParameters().expandExpressions(sql, getChangeSet().getChangeLog());
        }
        super.setSql(sql);
    }

    @Override
    public String getSerializedObjectNamespace() {
        return STANDARD_CHANGELOG_NAMESPACE;
    }

    @Override
    public String describe() {
        return "SQLFileChange{" +
                "path='" + path + '\'' +
                ", relativeToChangelogFile=" + relativeToChangelogFile +
                '}';
    }

    @Override
    public CheckSum generateCheckSum() {
        ChecksumVersion version = Scope.getCurrentScope().getChecksumVersion();
        if (version.lowerOrEqualThan(ChecksumVersion.V8)) {
            return super.generateCheckSum();
        }
        InputStream stream = null;
        try {
            String sqlContent = getSql(doExpandExpressionsInGenerateChecksum);
            Charset encoding = GlobalConfiguration.FILE_ENCODING.getCurrentValue();
            stream = new ByteArrayInputStream(sqlContent.getBytes(encoding));
            return CheckSum.compute(new AbstractSQLChange.NormalizingStream(stream), false);
        }
        finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException ignore) {
                    // Do nothing
                }
            }
        }
    }
}

        ValidationErrors validationErrors = new ValidationErrors();
        if (StringUtil.trimToNull(getPath()) == null) {
            validationErrors.addError("'path' is required");
        } else {
            try {
                Resource resource = getResource();
                if (!resource.exists()) {
                    alertOnNonExistantSqlFile(validationErrors);
                }
            } catch (IOException e) {
                Scope.getCurrentScope().getLog(getClass()).warning("Failed to obtain sqlFile resource at path '" + path + "'while attempting to validate the existence of the sqlFile.", e);
                alertOnNonExistantSqlFile(validationErrors);
            }
        }

        return validationErrors;
    
<methods>public liquibase.change.ChangeStatus checkStatus(liquibase.database.Database) ,public liquibase.change.CheckSum generateCheckSum() ,public boolean generateRollbackStatementsVolatile(liquibase.database.Database) ,public liquibase.statement.SqlStatement[] generateStatements(liquibase.database.Database) ,public boolean generateStatementsVolatile(liquibase.database.Database) ,public java.lang.String getDbms() ,public java.lang.String getEndDelimiter() ,public java.lang.String getSql() ,public java.lang.Boolean isIgnoreOriginalSplitStatements() ,public java.lang.Boolean isSplitStatements() ,public boolean isSplitStatementsSet() ,public java.lang.Boolean isStripComments() ,public java.io.InputStream openSqlStream() throws java.io.IOException,public void setDbms(java.lang.String) ,public void setEndDelimiter(java.lang.String) ,public void setIgnoreOriginalSplitStatements(java.lang.Boolean) ,public void setOriginalSplitStatements(java.lang.Boolean) ,public void setSplitStatements(java.lang.Boolean) ,public void setSql(java.lang.String) ,public void setStripComments(java.lang.Boolean) ,public boolean supports(liquibase.database.Database) ,public liquibase.exception.ValidationErrors validate(liquibase.database.Database) ,public liquibase.exception.Warnings warn(liquibase.database.Database) <variables>private java.lang.String dbms,protected java.lang.String encoding,private java.lang.String endDelimiter,private java.lang.Boolean ignoreOriginalSplitStatements,private java.lang.Boolean originalSplitStatements,private boolean splitStatements,private boolean splitStatementsSet,private java.lang.String sql,private boolean stripComments