├── src
├── test
│ ├── data
│ │ ├── invalid-syntax.sql
│ │ ├── drop-test-tables.sql
│ │ ├── delimiter-a.sql
│ │ ├── delimiter-b.sql
│ │ ├── delimiter-c.sql
│ │ └── create-test-tables.sql
│ ├── resources
│ │ └── test.properties
│ └── java
│ │ └── org
│ │ └── codehaus
│ │ └── mojo
│ │ └── sql
│ │ ├── CustomSqlExecMojo.java
│ │ ├── SqlSplitterTest.java
│ │ └── SqlExecMojoTest.java
├── it
│ ├── msql-51
│ │ ├── invoker.properties
│ │ ├── derby.properties
│ │ └── pom.xml
│ ├── msql-9
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── sql
│ │ │ │ └── filter.sql
│ │ └── pom.xml
│ ├── connection-retry
│ │ ├── invoker.properties
│ │ └── pom.xml
│ ├── msql-64
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── sql
│ │ │ │ ├── sql2.sql
│ │ │ │ └── sql1.sql
│ │ ├── verify.groovy
│ │ └── pom.xml
│ ├── setup
│ │ ├── invoker.properties
│ │ └── pom.xml
│ ├── msql-52
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── sql
│ │ │ │ └── oracle-sqlplus.sql
│ │ ├── pre-execute.groovy
│ │ ├── verify.groovy
│ │ └── pom.xml
│ ├── settings-security.xml
│ ├── derby
│ │ ├── src
│ │ │ └── test
│ │ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── codehaus
│ │ │ │ └── mojo
│ │ │ │ └── sql
│ │ │ │ └── QueryTest.java
│ │ └── pom.xml
│ ├── msql-76
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── sql
│ │ │ │ ├── post-execute.groovy
│ │ │ │ └── pre-execute.groovy
│ │ ├── verify.groovy
│ │ └── pom.xml
│ ├── msql-85
│ │ ├── verify.groovy
│ │ └── pom.xml
│ └── settings.xml
├── main
│ ├── resources
│ │ └── META-INF
│ │ │ └── m2e
│ │ │ └── lifecycle-mapping-metadata.xml
│ └── java
│ │ └── org
│ │ └── codehaus
│ │ └── mojo
│ │ └── sql
│ │ ├── Fileset.java
│ │ ├── DelimiterType.java
│ │ ├── SqlSplitter.java
│ │ └── SqlExecMojo.java
└── site
│ ├── apt
│ ├── examples
│ │ ├── settings.apt
│ │ └── execute.apt.vm
│ ├── index.apt
│ └── usage.apt.vm
│ ├── fml
│ └── faq.fml
│ └── site.xml
├── .gitignore
├── .github
├── release-drafter.yml
├── workflows
│ ├── maven.yml
│ └── release-drafter.yml
└── dependabot.yml
├── .git-blame-ignore-revs
├── README.md
├── pom.xml
└── LICENSE.txt
/src/test/data/invalid-syntax.sql:
--------------------------------------------------------------------------------
1 | create table bogus;
2 |
3 |
--------------------------------------------------------------------------------
/src/it/msql-51/invoker.properties:
--------------------------------------------------------------------------------
1 | invoker.goals = clean test
2 |
--------------------------------------------------------------------------------
/src/it/msql-9/src/main/sql/filter.sql:
--------------------------------------------------------------------------------
1 | select ${one} FROM SYSIBM.SYSDUMMY1;
--------------------------------------------------------------------------------
/src/it/connection-retry/invoker.properties:
--------------------------------------------------------------------------------
1 | invoker.goals = clean install
2 |
--------------------------------------------------------------------------------
/src/it/msql-64/src/main/sql/sql2.sql:
--------------------------------------------------------------------------------
1 | SELECT COUNT( GROUP_ID ) from GROUPS;
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | .settings
3 | .classpath
4 | .project
5 | *.iml
6 | .idea
7 |
8 |
--------------------------------------------------------------------------------
/src/it/msql-51/derby.properties:
--------------------------------------------------------------------------------
1 | derby.connection.requireAuthentication=true
2 | derby.user.me=mine
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | _extends: .github
2 | tag-template: sql-maven-plugin-$NEXT_MINOR_VERSION
3 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # code reformat after spotless enabled
2 | 247a02c5c43829b766f038a1dd17d6112be94f25
3 |
--------------------------------------------------------------------------------
/src/it/setup/invoker.properties:
--------------------------------------------------------------------------------
1 | invoker.name = Setup Integration Test Parent
2 | invoker.goals = install
3 |
--------------------------------------------------------------------------------
/src/test/data/drop-test-tables.sql:
--------------------------------------------------------------------------------
1 | drop table PERSON;
2 | drop table PLACE;
3 | drop table PERSON_PLACE_MAP;
4 |
5 |
--------------------------------------------------------------------------------
/src/test/data/delimiter-a.sql:
--------------------------------------------------------------------------------
1 | DELIMITER ¤
2 | create table DELIM_A ( ID integer )¤
3 | create table DELIM_A2 ( ID integer )¤
4 |
--------------------------------------------------------------------------------
/src/test/data/delimiter-b.sql:
--------------------------------------------------------------------------------
1 | DELIMITER #
2 | create table DELIM_B ( ID integer )#
3 | create table DELIM_B2 ( ID integer )#
4 |
--------------------------------------------------------------------------------
/src/test/data/delimiter-c.sql:
--------------------------------------------------------------------------------
1 | DELIMITER £
2 | create table DELIM_C ( ID integer )£
3 | create table DELIM_C2 ( ID integer )£
4 |
--------------------------------------------------------------------------------
/src/it/msql-52/src/main/sql/oracle-sqlplus.sql:
--------------------------------------------------------------------------------
1 | DROP SEQUENCE &&schema..ACCOUNT_CODE_DEFINITION_SEQ;
2 |
3 | SELECT &1 FROM DUAL;
--------------------------------------------------------------------------------
/src/it/settings-security.xml:
--------------------------------------------------------------------------------
1 |
2 | {vSyf3GushWgHLQbM0Rzrsy3AbQIlHkUYP8wcNsPWAYU=}
3 |
--------------------------------------------------------------------------------
/src/test/resources/test.properties:
--------------------------------------------------------------------------------
1 | driver=org.hsqldb.jdbc.JDBCDriver
2 | user=sa
3 | password=
4 | url=jdbc:hsqldb:mem:egdb
5 | driverProperties=key1=value1,key2=value2
6 |
--------------------------------------------------------------------------------
/src/it/msql-64/src/main/sql/sql1.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE GROUPS (GROUP_ID SMALLINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 5, INCREMENT BY 5), ADDRESS VARCHAR(100), PHONE VARCHAR(15));
2 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | name: GitHub CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | build:
9 | name: Verify
10 | uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v4
11 |
--------------------------------------------------------------------------------
/.github/workflows/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name: Release Drafter
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | update_release_draft:
8 | uses: apache/maven-gh-actions-shared/.github/workflows/release-drafter.yml@v4
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/src/test/data/create-test-tables.sql:
--------------------------------------------------------------------------------
1 | create table PERSON ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50) );
2 | create table PLACE ( PLACE_ID integer, CITY varchar(50), STATE varchar(50) );
3 | create table PERSON_PLACE_MAP ( PERSON_ID integer, PLACE_ID integer );
4 |
5 |
--------------------------------------------------------------------------------
/src/it/msql-52/pre-execute.groovy:
--------------------------------------------------------------------------------
1 | File outputFile = new File( basedir, 'target/oracle-sqlplus.sql')
2 | outputFile.getParentFile().mkdirs();
3 | new FileReader( 'src/main/sql/oracle-sqlplus.sql' ).transformLine( outputFile.newWriter() ) { line->
4 | line.replaceAll( '&1', 'customer' ).replaceAll( '&&schema..', 'SOME_SCHEMA.' )
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | execute
8 |
9 |
10 |
11 |
12 |
18 | false
19 | false
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/it/derby/src/test/java/org/codehaus/mojo/sql/QueryTest.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | import java.sql.Connection;
4 | import java.sql.Driver;
5 | import java.sql.ResultSet;
6 | import java.sql.Statement;
7 |
8 | import junit.framework.TestCase;
9 |
10 | public class QueryTest
11 | extends TestCase
12 | {
13 |
14 | public void testQuery()
15 | throws Exception
16 | {
17 | Class dc = Class.forName( "org.apache.derby.jdbc.EmbeddedDriver" );
18 | Driver driverInstance = (Driver) dc.newInstance();
19 |
20 | Connection conn = driverInstance.connect( "jdbc:derby:target/testdb", null );
21 |
22 | Statement st = conn.createStatement();
23 | ResultSet rs = st.executeQuery( "select count(*) from derbyDB" );
24 | rs.next();
25 | assertEquals( 2, rs.getInt(1) );
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MojoHaus SQL Maven Plugin
2 |
3 | This is the [sql-maven-plugin](http://www.mojohaus.org/sql-maven-plugin/).
4 |
5 | [](http://www.apache.org/licenses/)
6 | [](http://search.maven.org/#search%7Cga%7C1%7Csql-maven-plugin)
7 | [](https://travis-ci.org/mojohaus/sql-maven-plugin)
8 |
9 | ## Releasing
10 |
11 | * Make sure `gpg-agent` is running.
12 | * Execute `mvn -B release:prepare release:perform`
13 |
14 | For publishing the site do the following:
15 |
16 | ```
17 | cd target/checkout
18 | mvn verify site site:stage scm-publish:publish-scm
19 | ```
20 |
21 |
--------------------------------------------------------------------------------
/src/it/msql-76/src/main/sql/post-execute.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | assert true
--------------------------------------------------------------------------------
/src/it/msql-76/src/main/sql/pre-execute.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | assert true
--------------------------------------------------------------------------------
/src/it/msql-85/verify.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | File log = new File(basedir, 'target/logs/maven/sql.log')
20 | assert log.exists()
21 |
--------------------------------------------------------------------------------
/src/site/apt/examples/settings.apt:
--------------------------------------------------------------------------------
1 | ------
2 | Settings
3 | ------
4 | Dan T. Tran
5 |
6 | ------
7 | 2010-02-15
8 | ------
9 |
10 | Settings
11 |
12 | You can hide the username/password in your <<>>. Just ensure that you configure <<>> in your
13 | POM, otherwise it will use your database's URL as a lookup key.
14 |
15 | --------------------
16 |
17 | [...]
18 |
19 |
20 | sensibleKey
21 | postgres
22 | password
23 |
24 | [...]
25 |
26 | [...]
27 |
28 | --------------------
29 |
30 | * Encrypted passwords
31 |
32 | Since 1.5 it's also possible to use encrypted passwords the way Maven does. Follow the instruction of the {{{http://maven.apache.org/guides/mini/guide-encryption.html}encryption mini guide}}.
33 | Just like unencrypted passwords you have to be sure to set the <<>>, the plugin will detect if the password is encrypted or not.
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main/java/org/codehaus/mojo/sql/Fileset.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | /**
23 | * @author Brian Topping
24 | */
25 | public class Fileset extends org.codehaus.plexus.util.DirectoryScanner {}
26 |
--------------------------------------------------------------------------------
/src/it/msql-52/verify.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | File log = new File(basedir, 'target/oracle-sqlplus.sql')
20 | assert log.exists()
21 | assert log.readLines().get(0) == 'DROP SEQUENCE SOME_SCHEMA.ACCOUNT_CODE_DEFINITION_SEQ;'
22 | assert log.readLines().get(2) == 'SELECT customer FROM DUAL;'
--------------------------------------------------------------------------------
/src/it/msql-76/verify.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 |
20 | File log = new File(basedir, 'build.log')
21 | assert log.exists()
22 | assert log.text.contains( '[INFO] run pre-execute script' )
23 | assert log.text.contains( 'pre-execute.groovy' )
24 | assert log.text.contains( '[INFO] run post-execute script' )
25 | assert log.text.contains( 'post-execute.groovy' )
26 |
--------------------------------------------------------------------------------
/src/it/msql-64/verify.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | File log = new File(basedir, 'build.log')
20 | assert log.exists()
21 | assert log.getText().contains('CREATE TABLE GROUPS (GROUP_ID SMALLINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 5, INCREMENT BY 5), ADDRESS VARCHAR(100), PHONE VARCHAR(15))')
22 | assert log.getText().contains('SELECT COUNT( GROUP_ID ) from GROUPS')
--------------------------------------------------------------------------------
/src/test/java/org/codehaus/mojo/sql/CustomSqlExecMojo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 MojoHaus.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.codehaus.mojo.sql;
17 |
18 | import java.io.PrintStream;
19 | import java.sql.ResultSet;
20 | import java.sql.SQLException;
21 |
22 | public class CustomSqlExecMojo extends SqlExecMojo {
23 | @Override
24 | protected void printResultSet(ResultSet rs, PrintStream out) throws SQLException {
25 | out.println("This is the way");
26 | }
27 |
28 | @Override
29 | protected void printResultSetCount(int updateCountTotal, PrintStream out) {
30 | out.println(updateCountTotal + " cows affected");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/site/fml/faq.fml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | When do I have to set the delimiterType?
5 |
6 | By default the sql-maven-plugin will check if a line contains the specified delimiter, which is by default a semicolon(;).
7 | More complex statements, such as a trigger creation, may contain semi-colons too, although these are not yet the end of the sql-statement.
8 | In such case you have to change the delimiterType to 'row'.
9 | Now the plugin will check if the complete row matches the delimiter, in order to recognize the end of the sql statement.
10 |
11 | An example when to use <delimiterType>row</delimiterType>:
12 |
13 | CREATE OR REPLACE FUNCTION my_trig()
14 | RETURNS "trigger" AS
15 | $BODY$
16 | declare
17 | t text;
18 | begin
19 | -- any plpgsql code here, where each line ends with a semicolon
20 | return NEW;
21 | end$BODY$
22 | LANGUAGE 'plpgsql' VOLATILE;
23 |
24 | Note: Delimiters within values or comments are not a problem, these will be ignored.
25 | Note: The sql will be parsed until the delimiter or the EOF.
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/it/msql-52/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo.sql.it
12 | sql-52
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 | org.apache.derby.jdbc.EmbeddedDriver
31 | jdbc:derby:${project.build.directory}/sql64db;create=true
32 | pre-execute.groovy
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
22 |
23 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/it/msql-85/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo.sql.it
12 | msql-85
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 | org.apache.derby.jdbc.EmbeddedDriver
31 | jdbc:derby:${project.build.directory}/testdb;create=true
32 | select 1 FROM SYSIBM.SYSDUMMY1;
33 | ${project.build.directory}/logs/maven/sql.log
34 |
35 |
36 |
37 |
38 |
39 |
40 | 1
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/it/msql-64/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo.sql.it
12 | sql-64
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 | org.apache.derby.jdbc.EmbeddedDriver
31 | jdbc:derby:${project.build.directory}/sql64db;create=true
32 |
33 | src/main/sql/sql1.sql
34 | src/main/sql/sql2.sql
35 |
36 |
37 |
38 |
39 |
40 |
41 | 1
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/it/msql-76/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo.sql.it
12 | sql-76
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 | org.apache.derby.jdbc.EmbeddedDriver
31 | jdbc:derby:${project.build.directory}/sql76db;create=true
32 | select 1 FROM SYSIBM.SYSDUMMY1;
33 | src/main/sql/pre-execute.groovy
34 | src/main/sql/post-execute.groovy
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/it/msql-9/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo.sql.it
12 | sql-9
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 | org.apache.derby.jdbc.EmbeddedDriver
31 | jdbc:derby:${project.build.directory}/testdb;create=true
32 | ${maven.test.skip}
33 | select ${one} FROM SYSIBM.SYSDUMMY1;
34 | true
35 |
36 | src/main/sql/filter.sql
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 1
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/site/apt/index.apt:
--------------------------------------------------------------------------------
1 | ------
2 | Introduction
3 | ------
4 | Dan T. Tran
5 |
6 | ------
7 | 2010-02-15
8 | ------
9 |
10 | SQL Maven Plugin
11 |
12 | Use this plugin to execute SQL statements in a combination of strings,
13 | a list of files and/or a set of files through <<>>, <<>>, and
14 | <<>> configurations respectively.
15 |
16 |
17 | * Goals Overview
18 |
19 | * {{{./execute-mojo.html}sql:execute}} Execute SQL statements.
20 |
21 | []
22 |
23 |
24 | * Usage
25 |
26 | General instructions on how to use the SQL Maven Plugin can be found on the {{{./usage.html}usage page}}.
27 |
28 | In case you still have questions regarding the plugin's usage, please feel
29 | free to contact the {{{./mail-lists.html}user mailing list}}. The posts to the mailing list are archived and could
30 | already contain the answer to your question as part of an older thread. Hence, it is also worth browsing/searching
31 | the {{{./mail-lists.html}mail archive}}.
32 |
33 | If you feel like the plugin is missing a feature or has a defect, you can fill a feature request or bug report in our
34 | {{{./issue-tracking.html}issue tracker}}. When creating a new issue, please provide a comprehensive description of your
35 | concern. Especially for fixing bugs it is crucial that the developers can reproduce your problem. For this reason,
36 | entire debug logs, POMs or most preferably little demo projects attached to the issue are very much appreciated.
37 | Of course, patches are most welcome too. Contributors can check out the project from our
38 | {{{./source-repository.html}source repository}} and will find supplementary information in the
39 | {{{http://maven.apache.org/guides/development/guide-helping.html}guide to helping with Maven}}.
40 |
41 |
42 | * Examples
43 |
44 | * {{{./examples/execute.html}Executions.}}
45 |
46 | * {{{./examples/settings.html}Hide username/password in <<>>.}}
47 |
48 | []
49 |
--------------------------------------------------------------------------------
/src/it/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
23 |
24 |
25 |
26 | my.server
27 | me
28 | {iFtD2TFFjzoHEDN1RxW21zEBYW0Gt7GwbsOm6yDS63s=}
29 |
30 |
31 |
32 |
33 |
34 | it-repo
35 |
36 |
37 | local.central
38 | @localRepositoryUrl@
39 |
40 | true
41 |
42 |
43 | true
44 |
45 |
46 |
47 |
48 |
49 | local.central
50 | @localRepositoryUrl@
51 |
52 | true
53 |
54 |
55 | true
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | it-repo
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/site/apt/usage.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Usage
3 | ------
4 | Dan T. Tran
5 |
6 | ------
7 | 2010-02-15
8 | ------
9 |
10 | Usage
11 |
12 | The execution of this plugin's goal can be bound to a phase of the build lifecycle.
13 |
14 | Use the <<<\>>> element inside the <<<\>>> element to specify the artifact that has your
15 | JDBC driver.
16 |
17 | ---------------------------
18 |
19 | [...]
20 |
21 | [...]
22 |
23 | [...]
24 |
25 | org.codehaus.mojo
26 | sql-maven-plugin
27 | ${project.version}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | [...]
45 |
46 |
47 |
48 |
49 |
50 |
51 | execute
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | [...]
61 |
62 |
63 |
64 |
65 | [...]
66 |
67 | [...]
68 |
69 |
70 | [...]
71 |
72 | [...]
73 |
74 | [...]
75 |
76 | ---------------------------
77 |
--------------------------------------------------------------------------------
/src/it/msql-51/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo
12 | sql-maven-plugin-test
13 | 0.0.1-SNAPSHOT
14 |
15 |
16 |
17 |
18 | org.codehaus.mojo
19 | sql-maven-plugin
20 | @project.version@
21 |
22 |
23 | org.apache.derby
24 | derby
25 | @derby.version@
26 |
27 |
28 |
29 |
30 |
31 | prepare
32 | initialize
33 |
34 | execute
35 |
36 |
37 | jdbc:derby:${project.build.directory}/testdb;create=true
38 | me
39 | mine
40 | CREATE TABLE PEOPLE (PERSON_ID INT NOT NULL GENERATED ALWAYS AS IDENTITY CONSTRAINT PEOPLE_PK PRIMARY KEY, PERSON VARCHAR(26));
41 |
42 |
43 |
44 |
45 | test
46 | test
47 |
48 | execute
49 |
50 |
51 | jdbc:derby:${project.build.directory}/testdb;create=false
52 | my.server
53 | SELECT * FROM PEOPLE;
54 |
55 |
56 |
57 |
58 | org.apache.derby.jdbc.EmbeddedDriver
59 |
60 |
61 |
62 |
63 |
64 |
65 | 1
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/it/setup/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | 4.0.0
8 |
9 | org.codehaus.mojo.it.sql
10 | sql-maven-plugin-parent
11 | 0.1
12 |
13 | pom
14 |
15 | Parent 0.1
16 |
17 |
18 | UTF-8
19 |
20 |
21 |
22 |
23 |
24 |
25 | junit
26 | junit
27 | 4.13.1
28 | test
29 |
30 |
31 | org.testng
32 | testng
33 | 6.8
34 | test
35 |
36 |
37 | log4j
38 | log4j
39 | 1.2.16
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | org.apache.maven.plugins
49 | maven-surefire-plugin
50 | 2.19.1
51 |
52 |
53 | org.apache.maven.plugins
54 | maven-compiler-plugin
55 | 3.5.1
56 |
57 |
58 | org.apache.maven.plugins
59 | maven-clean-plugin
60 | 3.0.0
61 |
62 |
63 | org.apache.maven.plugins
64 | maven-assembly-plugin
65 | 2.4
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-jar-plugin
70 | 3.0.2
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-install-plugin
75 | 2.5.2
76 |
77 |
78 | org.apache.maven.plugins
79 | maven-war-plugin
80 | 2.3
81 |
82 |
83 | org.apache.maven.plugins
84 | maven-resources-plugin
85 | 3.0.1
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/it/connection-retry/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo
12 | sql-maven-plugin-it-connection-retry
13 | 1.0-SNAPSHOT
14 |
15 | sql-maven-plugin Connection Retry Database Integration Test
16 |
17 |
18 |
19 | org.apache.derby
20 | derby
21 | @derby.version@
22 | test
23 |
24 |
25 | junit
26 | junit
27 | 4.13.1
28 | test
29 |
30 |
31 |
32 |
33 |
34 |
35 | org.codehaus.mojo
36 | sql-maven-plugin
37 | @project.version@
38 |
39 |
40 | org.apache.derby
41 | derby
42 | @derby.version@
43 |
44 |
45 |
46 | org.apache.derby.jdbc.EmbeddedDriver
47 | jdbc:derby:${project.build.directory}/testdb;create=true
48 |
49 |
50 |
51 |
52 | create-table
53 | process-test-resources
54 |
55 | execute
56 |
57 |
58 |
59 | create table retry(num int);
60 | create table another(num int);
61 |
62 |
63 |
64 |
65 |
66 |
67 | insert-data
68 | process-test-resources
69 |
70 | execute
71 |
72 |
73 |
74 | select 1 from retry
75 | select 1 from another
76 |
77 | 2
78 | 2
79 |
80 | insert into retry values (2024);
81 | insert into another values (42);
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/it/derby/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.codehaus.mojo.it.sql
7 | sql-maven-plugin-parent
8 | 0.1
9 |
10 |
11 | org.codehaus.mojo
12 | sql-maven-plugin-it-derby
13 | 1.0-SNAPSHOT
14 |
15 | sql-maven-plugin Derby Database Integration Test
16 |
17 |
18 |
19 | org.apache.derby
20 | derby
21 | @derby.version@
22 | test
23 |
24 |
25 | junit
26 | junit
27 | 4.13.1
28 | test
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | org.codehaus.mojo
40 | sql-maven-plugin
41 | @project.version@
42 |
43 |
44 |
45 | org.apache.derby
46 | derby
47 | @derby.version@
48 |
49 |
50 |
51 |
52 | org.apache.derby.jdbc.EmbeddedDriver
53 | jdbc:derby:${project.build.directory}/testdb;create=true
54 | ${maven.test.skip}
55 |
56 |
57 |
58 |
59 |
60 | create-table
61 | process-test-resources
62 |
63 | execute
64 |
65 |
66 | create table derbyDB(num int, addr varchar(40))
67 |
68 |
69 |
70 |
71 | update-table
72 | process-test-resources
73 |
74 | execute
75 |
76 |
77 |
78 | insert into derbyDB values (1956,'Webster St.');
79 | insert into derbyDB values (1910,'Union St.');
80 | update derbyDB set num=180, addr='Grand Ave.' where num=1956;
81 |
82 |
83 |
84 |
85 |
86 | shutdown-database-so-that-test-can-run
87 | process-test-resources
88 |
89 | execute
90 |
91 |
92 | jdbc:derby:;shutdown=true
93 | true
94 |
95 |
96 |
97 |
98 | drop-table
99 | test
100 |
101 | execute
102 |
103 |
104 | drop table derbyDB
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/main/java/org/codehaus/mojo/sql/DelimiterType.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | /**
23 | * @author Brian Topping
24 | */
25 | public class DelimiterType {
26 | public static final String NORMAL = "normal";
27 |
28 | public static final String ROW = "row";
29 |
30 | /**
31 | * The selected value in this enumeration.
32 | */
33 | protected String value;
34 |
35 | /**
36 | * the index of the selected value in the array.
37 | */
38 | private int index = -1;
39 |
40 | /**
41 | * This is the only method a subclass needs to implement.
42 | *
43 | * @return an array holding all possible values of the enumeration. The order of elements must be fixed so that
44 | * indexOfValue(String) always return the same index for the same value.
45 | */
46 | public String[] getValues() {
47 | return new String[] {NORMAL, ROW};
48 | }
49 |
50 | /** bean constructor */
51 | protected DelimiterType() {}
52 |
53 | /**
54 | * Set the delimiterValue. Use DelimiterType.NORMAL or DelimiterType.ROW
55 | *
56 | * @param value
57 | */
58 | public final void setValue(String value) {
59 | int index = indexOfValue(value);
60 | if (index == -1) {
61 | throw new IllegalArgumentException(value + " is not a legal value for this attribute");
62 | }
63 | this.index = index;
64 | this.value = value;
65 | }
66 |
67 | /**
68 | * Is this value included in the enumeration?
69 | *
70 | * @param value
71 | * @return true if this value is supported
72 | */
73 | public final boolean containsValue(String value) {
74 | return (indexOfValue(value) != -1);
75 | }
76 |
77 | /**
78 | * get the index of a value in this enumeration.
79 | *
80 | * @param value the string value to look for.
81 | * @return the index of the value in the array of strings or -1 if it cannot be found.
82 | * @see #getValues()
83 | */
84 | public final int indexOfValue(String value) {
85 | String[] values = getValues();
86 | if (values == null || value == null) {
87 | return -1;
88 | }
89 | for (int i = 0; i < values.length; i++) {
90 | if (value.equals(values[i])) {
91 | return i;
92 | }
93 | }
94 | return -1;
95 | }
96 |
97 | /**
98 | * @return the selected value.
99 | */
100 | public final String getValue() {
101 | return value;
102 | }
103 |
104 | /**
105 | * @return the index of the selected value in the array.
106 | * @see #getValues()
107 | */
108 | public final int getIndex() {
109 | return index;
110 | }
111 |
112 | /**
113 | * Convert the value to its string form.
114 | *
115 | * @return the string form of the value.
116 | */
117 | public String toString() {
118 | return getValue();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/site/apt/examples/execute.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Executions
3 | ------
4 | Dan T. Tran
5 |
6 | ------
7 | 2010-02-15
8 | ------
9 |
10 | Executions
11 |
12 |
13 | The following build configuration shows how to drop/create a database and schema,
14 | then populate it before the <<>> phase, and drop the database after the <<>> phase.
15 |
16 | -------------------
17 |
18 | [...]
19 |
20 | [...]
21 |
22 |
23 | org.codehaus.mojo
24 | sql-maven-plugin
25 | ${project.version}
26 |
27 |
28 |
29 |
30 | postgresql
31 | postgresql
32 | 8.1-407.jdbc3
33 |
34 |
35 |
36 |
37 |
38 | org.postgresql.Driver
39 | jdbc:postgressql://localhost:5432:yourdb
40 | postgres
41 | password
42 |
45 | sensibleKey
46 |
47 | ${maven.test.skip}
48 |
49 |
50 |
51 |
52 | drop-db-before-test-if-any
53 | process-test-resources
54 |
55 | execute
56 |
57 |
58 |
59 | jdbc:postgressql://localhost:5432:bootstrapdb
60 | true
61 | drop database yourdb
62 |
63 | continue
64 |
65 |
66 |
67 |
68 | create-db
69 | process-test-resources
70 |
71 | execute
72 |
73 |
74 | jdbc:postgressql://localhost:5432:yourdb
75 |
76 | true
77 | create database yourdb
78 |
79 |
80 |
81 |
82 | create-schema
83 | process-test-resources
84 |
85 | execute
86 |
87 |
88 | true
89 |
90 | src/main/sql/your-schema.sql
91 |
92 |
93 |
94 |
95 |
96 | create-data
97 | process-test-resources
98 |
99 | execute
100 |
101 |
102 | ascending
103 |
104 | ${basedir}
105 |
106 | src/test/sql/test-data2.sql
107 | src/test/sql/test-data1.sql
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | drop-db-after-test
116 | test
117 |
118 | execute
119 |
120 |
121 | true
122 | drop database yourdb
123 |
124 |
125 |
126 |
127 | [...]
128 |
129 | [...]
130 |
131 | [...]
132 |
133 | -------------------
134 |
--------------------------------------------------------------------------------
/src/main/java/org/codehaus/mojo/sql/SqlSplitter.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | import org.codehaus.plexus.util.StringUtils;
23 |
24 | /**
25 | * Utility class to split a long sql batch script into single SQL commands.
26 | */
27 | public final class SqlSplitter {
28 | private SqlSplitter() {
29 | // hide utility class constructor
30 | }
31 |
32 | /**
33 | * Value indicating the sql has no end-delimiter like i.e. the semicolon.
34 | */
35 | public static final int NO_END = -1;
36 |
37 | /**
38 | * parsed sql started a single quote static text which continues on the next line (did not end)
39 | */
40 | public static final int OVERFLOW_SINGLE_QUOTE = -2;
41 |
42 | /**
43 | * parsed sql started a double quote static text which continues on the next line (did not end)
44 | */
45 | public static final int OVERFLOW_DOUBLE_QUOTE = -4;
46 |
47 | /**
48 | * parsed sql started a comment with /_* which continues on the next line (did not end)
49 | */
50 | public static final int OVERFLOW_COMMENT = -8;
51 |
52 | /**
53 | * Check if the given sql line contains a delimiter representing the end of the command. Please note that we do
54 | * not fully parse the SQL, so if we get a malformed statement, we cannot detect it.
55 | *
56 | * @param line to parse
57 | * @param delimiter which should be used to split SQL commands
58 | * @param overflowValue 0=none, {@link SqlSplitter#OVERFLOW_COMMENT}, {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE} or
59 | * {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE}
60 | * @return position after the end character if the given line contains the end of a SQL script,
61 | * {@link SqlSplitter#NO_END} if it doesn't contain an end char. {@link SqlSplitter#OVERFLOW_SINGLE_QUOTE}
62 | * will be returned if a single quote didn't get closed, {@link SqlSplitter#OVERFLOW_DOUBLE_QUOTE} likewise
63 | * for not closed double quotes.
64 | */
65 | public static int containsSqlEnd(String line, String delimiter, final int overflowValue) {
66 | int ret = overflowValue >= 0 ? NO_END : overflowValue;
67 |
68 | // / * * / comments
69 | boolean isComment = (overflowValue == OVERFLOW_COMMENT);
70 |
71 | String quoteChar = null;
72 | if (overflowValue == OVERFLOW_SINGLE_QUOTE) {
73 | quoteChar = "'";
74 | } else if (overflowValue == OVERFLOW_DOUBLE_QUOTE) {
75 | quoteChar = "\"";
76 | }
77 |
78 | boolean isAlphaDelimiter = StringUtils.isAlpha(delimiter);
79 |
80 | if (line == null || line.length() == 0) {
81 | return ret;
82 | }
83 |
84 | int pos = 0;
85 | int maxpos = line.length() - 1;
86 |
87 | char c1;
88 | char c2 = line.charAt(0);
89 | statement:
90 | do {
91 | if (isComment) {
92 | do {
93 | // keep c2 in line
94 | if (pos < maxpos) {
95 | c2 = line.charAt(pos + 1);
96 | }
97 |
98 | if (startsWith(line, '*', pos) && startsWith(line, '/', pos + 1)) {
99 | ret = NO_END;
100 | isComment = false;
101 | pos++;
102 |
103 | continue statement;
104 | }
105 | } while (pos++ < maxpos);
106 |
107 | // reached EOL
108 | break statement;
109 | }
110 |
111 | // if in quote-mode, search for end quote, respecting escaped characters
112 | if (quoteChar != null) {
113 | String doubleQuote = quoteChar + quoteChar;
114 | do {
115 | // keep c2 in line
116 | if (pos < maxpos) {
117 | c2 = line.charAt(pos + 1);
118 | }
119 |
120 | if (startsWith(line, "\\", pos) || startsWith(line, doubleQuote, pos)) {
121 | // skip next character, but stay in quote-mode
122 | pos++;
123 | } else if (startsWith(line, quoteChar, pos)) {
124 | ret = NO_END;
125 | quoteChar = null;
126 |
127 | continue statement;
128 | }
129 | } while (pos++ < maxpos);
130 |
131 | // reach EOL
132 | break statement;
133 | }
134 |
135 | // use the nextchar from the previous iteration
136 | c1 = c2;
137 | if (pos < maxpos) {
138 | // and set the following char
139 | c2 = line.charAt(pos + 1);
140 | } else {
141 | // or reset to blank if the line has ended
142 | c2 = ' ';
143 | }
144 |
145 | // verify if current char indicates start of new quoted block
146 | if (c1 == '\'' || c1 == '"') {
147 | quoteChar = String.valueOf(c1);
148 | ret = quoteChar.equals("'") ? OVERFLOW_SINGLE_QUOTE : OVERFLOW_DOUBLE_QUOTE;
149 | continue statement;
150 | }
151 |
152 | // parse for a / * start of comment
153 | if (c1 == '/' && c2 == '*') {
154 | isComment = true;
155 | pos++;
156 | ret = OVERFLOW_COMMENT;
157 | continue statement;
158 | }
159 |
160 | if (c1 == '-' && c2 == '-') {
161 | return ret;
162 | }
163 |
164 | if (startsWith(line, delimiter, pos)) {
165 | if (isAlphaDelimiter) {
166 | // check if delimiter is at start or end of line, surrounded
167 | // by non-alpha character
168 | if ((pos == 0 || !isAlpha(line.charAt(pos - 1)))
169 | && (line.length() == pos + delimiter.length()
170 | || !isAlpha(line.charAt(pos + delimiter.length())))) {
171 | return pos + delimiter.length();
172 | }
173 | } else {
174 | return pos + delimiter.length();
175 | }
176 | }
177 |
178 | } while (maxpos > pos++);
179 |
180 | return ret;
181 | }
182 |
183 | /**
184 | * Small performance optimized replacement for {@link String#startsWith(String, int)}.
185 | *
186 | * @param toParse the String to parse
187 | * @param delimiter the delimiter to look for
188 | * @param position the initial position to start the scan with
189 | */
190 | private static boolean startsWith(String toParse, String delimiter, int position) {
191 | if (delimiter.length() == 1) {
192 | return toParse.length() > position && toParse.charAt(position) == delimiter.charAt(0);
193 | } else {
194 | return toParse.startsWith(delimiter, position);
195 | }
196 | }
197 |
198 | /**
199 | * @param toParse the String to parse
200 | * @param delimiter the delimiter to look for
201 | * @param position the initial position to start the scan with
202 | * @return
203 | */
204 | private static boolean startsWith(String toParse, char delimiter, int position) {
205 | return toParse.length() > position && toParse.charAt(position) == delimiter;
206 | }
207 |
208 | /**
209 | * @param c the char to check
210 | * @return true if the given character is either a lower or an upperchase alphanumerical character
211 | */
212 | private static boolean isAlpha(char c) {
213 | return Character.isUpperCase(c) || Character.isLowerCase(c);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 4.0.0
5 |
6 |
7 | org.codehaus.mojo
8 | mojo-parent
9 | 94
10 |
11 |
12 | sql-maven-plugin
13 | 3.1.0-SNAPSHOT
14 | maven-plugin
15 | SQL Maven Plugin
16 | Execute SQL Statements
17 | 2006
18 |
19 |
20 |
21 | Apache License 2
22 | http://www.apache.org/licenses/LICENSE-2.0.txt
23 | repo
24 |
25 |
26 |
27 |
28 |
29 | topping
30 | Brian Topping
31 | topping@codehaus.org
32 |
33 | Java Developer
34 |
35 | +8
36 |
37 |
38 | dtran
39 | Dan Tran
40 | dantran@apache.org
41 |
42 | Java Developer
43 |
44 |
45 |
46 | struberg
47 | Mark Struberg
48 | struberg@yahoo.de
49 |
50 | Java Developer
51 |
52 | +1
53 |
54 |
55 | rfscholte
56 | Robert Scholte
57 | rfscholte@codehaus.org
58 |
59 | Java Developer
60 |
61 | Europe/Amsterdam
62 |
63 |
64 | khmarbaise
65 | Karl-Heinz Marbaise
66 | khmarbaise@apache.org
67 |
68 | Java Developer
69 |
70 | Europe/Berlin
71 |
72 |
73 | Vaibhav
74 | Vaibhav Singh
75 | vbhav.singh@gmail.com
76 |
77 | Java Developer
78 |
79 | America/New_york
80 |
81 |
82 |
83 |
84 | ${mavenVersion}
85 |
86 |
87 | scm:git:https://github.com/mojohaus/sql-maven-plugin.git
88 | scm:git:ssh://git@github.com/mojohaus/sql-maven-plugin.git
89 | HEAD
90 | https://github.com/mojohaus/sql-maven-plugin/
91 |
92 |
93 |
94 | github
95 | https://github.com/mojohaus/${project.artifactId}/issues
96 |
97 |
98 |
99 |
100 | 10.14.2.0
101 | target/staging/${project.artifactId}
102 |
103 |
104 |
105 |
106 |
107 | org.codehaus.plexus
108 | plexus-utils
109 | 4.0.2
110 |
111 |
112 |
113 | org.apache.groovy
114 | groovy
115 | 5.0.1
116 |
117 |
118 |
119 |
120 |
121 | junit
122 | junit
123 | test
124 |
125 |
126 | org.apache.maven.plugin-tools
127 | maven-plugin-annotations
128 | provided
129 |
130 |
131 | org.apache.maven
132 | maven-plugin-api
133 | ${mavenVersion}
134 | provided
135 |
136 |
137 | org.apache.maven
138 | maven-core
139 | ${mavenVersion}
140 | provided
141 |
142 |
143 | org.sonatype.plexus
144 | plexus-sec-dispatcher
145 |
146 |
147 |
148 |
149 | org.apache.maven
150 | maven-settings
151 | ${mavenVersion}
152 | provided
153 |
154 |
155 | org.apache.maven.shared
156 | maven-script-interpreter
157 | 1.6
158 |
159 |
160 | org.codehaus.plexus
161 | plexus-utils
162 |
163 |
164 | org.codehaus.plexus
165 | plexus-xml
166 | 3.0.1
167 |
168 |
169 | org.codehaus.plexus
170 | plexus-interpolation
171 | 1.28
172 |
173 |
174 |
175 | org.apache.commons
176 | commons-lang3
177 | 3.19.0
178 |
179 |
180 | org.apache.commons
181 | commons-text
182 | 1.14.0
183 |
184 |
185 | org.apache.maven.shared
186 | maven-filtering
187 | 3.4.0
188 |
189 |
190 | org.codehaus.plexus
191 | plexus-sec-dispatcher
192 | 2.0
193 |
194 |
195 | org.apache.maven
196 | maven-compat
197 | ${mavenVersion}
198 | test
199 |
200 |
201 | org.apache.maven.plugin-testing
202 | maven-plugin-testing-harness
203 | 3.3.0
204 | test
205 |
206 |
207 | org.hsqldb
208 | hsqldb
209 | 2.7.4
210 | jdk8
211 | test
212 |
213 |
214 | org.slf4j
215 | slf4j-simple
216 | 1.7.36
217 | test
218 |
219 |
220 |
221 |
222 |
223 | run-its
224 |
225 |
226 |
227 | org.apache.maven.plugins
228 | maven-invoker-plugin
229 |
230 | true
231 | ${project.build.directory}/it
232 |
233 | verify
234 | ${project.build.directory}/local-repo
235 | src/it/settings.xml
236 |
237 | ${project.basedir}/src/it/settings-security.xml
238 |
239 |
240 | clean
241 | sql:execute
242 |
243 |
244 |
245 |
246 |
247 | install
248 | run
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/test/java/org/codehaus/mojo/sql/SqlSplitterTest.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | import java.io.BufferedReader;
23 | import java.io.StringReader;
24 | import java.util.logging.Logger;
25 |
26 | import junit.framework.TestCase;
27 |
28 | public class SqlSplitterTest extends TestCase {
29 |
30 | public void testContainsSqlString() throws Exception {
31 | containsNot("");
32 | containsNot(" ");
33 | containsNot(" \t ");
34 |
35 | contains(";", 1);
36 | contains("SELECT * from myTable;", 22);
37 |
38 | contains("SELECT * from myTable; -- with sl comment", 22);
39 | contains("SELECT * from myTable; /* with part comment */", 22);
40 |
41 | contains("SELECT * from myTable /* with part comment inside*/ ; ", 54);
42 |
43 | contains("SELECT * from myTable /* with ; semicolon*/ ; ", 46);
44 |
45 | contains("INSERT INTO testTable (thevalue) VALUES ('value !'); -- comment at the end", 53);
46 |
47 | // a " inside a ' quoted text
48 | contains("INSERT INTO testTable (thevalue) VALUES ('value \" !');", 55);
49 |
50 | // a ' inside a " quoted text
51 | contains("INSERT INTO testTable (thevalue) VALUES (\"value ' !\");", 55);
52 |
53 | contains("INSERT INTO testTable (thevalue) VALUES (\"value -- \");", 55);
54 | contains("INSERT INTO testTable (thevalue) VALUES ('value -- ');", 55);
55 |
56 | containsNot("SELECT * from myTable where value = ';' AND -- semicolon is quoted!");
57 |
58 | contains("INSERT INTO testTable (thevalue) VALUES (' text '' other ');", 60);
59 |
60 | //
61 | contains("INSERT INTO testTable (thevalue) VALUES ('value !'); -- comment with ' single quote", 53);
62 | contains("SELECT * from myTable /* comment with ' single quote */ ; ", 58);
63 | }
64 |
65 | /**
66 | * This unit test is meant for checking the performance with a profiler
67 | */
68 | public void testSplitterPerformance() throws Exception {
69 | long startTime = System.currentTimeMillis();
70 | for (int i = 0; i < 10000; i++) {
71 | contains(
72 | "INSERT INTO testTable (thevalue1, thevalue2, anothervalue3, justmore, andevenmore) "
73 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
74 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
75 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
76 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
77 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
78 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
79 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
80 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
81 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
82 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
83 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
84 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
85 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
86 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
87 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
88 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
89 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
90 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
91 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
92 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
93 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
94 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
95 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
96 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
97 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
98 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
99 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
100 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
101 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
102 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
103 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
104 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
105 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
106 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
107 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
108 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
109 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
110 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
111 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
112 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
113 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
114 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
115 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
116 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
117 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
118 | + "/*it might also contain lots of other really loooooong and useless comments...*/ "
119 | + "VALUES ('value !', 'else', 'more', 'hopefullyfast');",
120 | 3861);
121 | }
122 |
123 | long duration = System.currentTimeMillis() - startTime;
124 | Logger log = Logger.getLogger(SqlSplitterTest.class.getName());
125 | log.info("SqlPlitterTest performance took [ms]: " + duration);
126 | }
127 |
128 | public void testMsSQLStrings() throws Exception {
129 | String del = "GO";
130 |
131 | containsNot("SELECT COUNT(*) FROM Logs", del);
132 | containsNot("SELECT * FROM TPersons", del);
133 | contains("GO", del, 2);
134 | }
135 |
136 | // MSQL-48
137 | public void testSQLContainingRegExp() throws Exception {
138 | String sql = "EXECUTE IMMEDIATE 'PROCEDURE my_sproc(' ||\r\n"
139 | + "' ...' ||\r\n" + "' ...' ||\r\n"
140 | + "' ...REGEXP_INSTR(v_foo, '''^[A-Za-z0-9]{2}[0-9]{3,4}$''') ...' ||\r\n"
141 | + "'...' ||\r\n"
142 | + "'EXCEPTION' ||\r\n" + "'WHEN OTHERS THEN' ||\r\n"
143 | + "' DBMS_OUTPUT.put_line (''Error stack at top level:'');' ||\r\n"
144 | + "' putline (DBMS_UTILITY.format_error_backtrace);' ||\r\n"
145 | + "' bt.show_info (DBMS_UTILITY.format_error_backtrace);' ||\r\n"
146 | + "'END my_sproc;'";
147 | BufferedReader in = new BufferedReader(new StringReader(sql));
148 |
149 | // Only checking if this complex statement can be parsed
150 | String line;
151 | int lineNr = 0;
152 | for (; (line = in.readLine()) != null; lineNr++) {
153 | SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END);
154 | }
155 | assertEquals("Not every line is parsed", 11, lineNr);
156 | }
157 |
158 | public void testOverflows() {
159 | assertEquals(
160 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
161 | SqlSplitter.containsSqlEnd("test 'with an open singlequote statics;", ";", SqlSplitter.NO_END));
162 | assertEquals(
163 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
164 | SqlSplitter.containsSqlEnd("test 'with an open singlequote statics;lalala", ";", SqlSplitter.NO_END));
165 |
166 | assertEquals(
167 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
168 | SqlSplitter.containsSqlEnd("test \"with an open doublequote statics;", ";", SqlSplitter.NO_END));
169 | assertEquals(
170 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
171 | SqlSplitter.containsSqlEnd("test \"with an open doublequote statics;lalala", ";", SqlSplitter.NO_END));
172 |
173 | assertEquals(
174 | 39,
175 | SqlSplitter.containsSqlEnd(
176 | "test \"with an open doublequote statics;", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
177 |
178 | assertEquals(
179 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
180 | SqlSplitter.containsSqlEnd(
181 | "test \"with an open doublequote statics;", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
182 | assertEquals(
183 | 40,
184 | SqlSplitter.containsSqlEnd(
185 | "test \"with an open doublequote statics';", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
186 |
187 | assertEquals(
188 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
189 | SqlSplitter.containsSqlEnd(
190 | "test 'with an open singlequote statics;", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
191 | assertEquals(
192 | 40,
193 | SqlSplitter.containsSqlEnd(
194 | "test 'with an open singlequote statics\";", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
195 |
196 | assertEquals(
197 | SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("test /* comment;", ";", SqlSplitter.NO_END));
198 | assertEquals(
199 | SqlSplitter.OVERFLOW_COMMENT,
200 | SqlSplitter.containsSqlEnd("comment; continued", ";", SqlSplitter.OVERFLOW_COMMENT));
201 | assertEquals(16, SqlSplitter.containsSqlEnd("test */ comment;", ";", SqlSplitter.OVERFLOW_COMMENT));
202 |
203 | // test value divided over 2 lines, second line hits a comment first
204 | assertEquals(
205 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
206 | SqlSplitter.containsSqlEnd("INSERT INTO topics VALUES( 'did you know: ", ";", SqlSplitter.NO_END));
207 | assertEquals(
208 | 33,
209 | SqlSplitter.containsSqlEnd(
210 | "javadoc always starts with /**');", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
211 |
212 | // bare minimums
213 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd("'", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
214 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd("\"", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
215 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd("*/", ";", SqlSplitter.OVERFLOW_COMMENT));
216 |
217 | assertEquals(SqlSplitter.OVERFLOW_SINGLE_QUOTE, SqlSplitter.containsSqlEnd("'", ";", SqlSplitter.NO_END));
218 | assertEquals(SqlSplitter.OVERFLOW_DOUBLE_QUOTE, SqlSplitter.containsSqlEnd("\"", ";", SqlSplitter.NO_END));
219 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("/*", ";", SqlSplitter.NO_END));
220 |
221 | assertEquals(
222 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
223 | SqlSplitter.containsSqlEnd("", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
224 | assertEquals(
225 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
226 | SqlSplitter.containsSqlEnd("\"", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
227 | assertEquals(
228 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
229 | SqlSplitter.containsSqlEnd("/*", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
230 | assertEquals(
231 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
232 | SqlSplitter.containsSqlEnd("*/", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
233 | assertEquals(
234 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
235 | SqlSplitter.containsSqlEnd("''", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
236 | assertEquals(
237 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
238 | SqlSplitter.containsSqlEnd("\"\"", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
239 |
240 | assertEquals(
241 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
242 | SqlSplitter.containsSqlEnd("", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
243 | assertEquals(
244 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
245 | SqlSplitter.containsSqlEnd("'", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
246 | assertEquals(
247 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
248 | SqlSplitter.containsSqlEnd("/*", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
249 | assertEquals(
250 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
251 | SqlSplitter.containsSqlEnd("*/", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
252 | assertEquals(
253 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
254 | SqlSplitter.containsSqlEnd("''", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
255 | assertEquals(
256 | SqlSplitter.OVERFLOW_DOUBLE_QUOTE,
257 | SqlSplitter.containsSqlEnd("\"\"", ";", SqlSplitter.OVERFLOW_DOUBLE_QUOTE));
258 |
259 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("", ";", SqlSplitter.OVERFLOW_COMMENT));
260 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("'", ";", SqlSplitter.OVERFLOW_COMMENT));
261 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("\"", ";", SqlSplitter.OVERFLOW_COMMENT));
262 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("/*", ";", SqlSplitter.OVERFLOW_COMMENT));
263 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("''", ";", SqlSplitter.OVERFLOW_COMMENT));
264 | assertEquals(
265 | SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd("\"\"", ";", SqlSplitter.OVERFLOW_COMMENT));
266 |
267 | // escaped escape character
268 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd("\\\\'", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
269 | assertEquals(
270 | SqlSplitter.OVERFLOW_SINGLE_QUOTE,
271 | SqlSplitter.containsSqlEnd("\\'", ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
272 | }
273 |
274 | public void testAlphaDelimiter() throws Exception {
275 | assertEquals(2, SqlSplitter.containsSqlEnd("go", "go", SqlSplitter.NO_END));
276 | assertEquals(2, SqlSplitter.containsSqlEnd("Go", "Go", SqlSplitter.NO_END));
277 | assertEquals(5, SqlSplitter.containsSqlEnd(" GO", "GO", SqlSplitter.NO_END));
278 | assertEquals(2, SqlSplitter.containsSqlEnd("GO ", "GO", SqlSplitter.NO_END));
279 | }
280 |
281 | /**
282 | * Test a problem with single quotes split over multiple lines
283 | *
284 | * @throws Exception
285 | */
286 | public void testSqlSingleQuotesInDifferentLines() throws Exception {
287 | // @formatter:off
288 | String sql = "BEGIN\n"
289 | + "requete='INSERT INTO rid_oid(rid,oids)\n"
290 | + " VALUE('||quote_literal(rid)||',' \n"
291 | + " ||quote_literal(Oid)||')';\n"
292 | + "EXECUTE requete;";
293 | // @formatter:on
294 |
295 | BufferedReader in = new BufferedReader(new StringReader(sql));
296 |
297 | // Only checking if this complex statement can be parsed
298 | String line;
299 |
300 | line = in.readLine();
301 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END));
302 |
303 | line = in.readLine();
304 | assertEquals(SqlSplitter.OVERFLOW_SINGLE_QUOTE, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END));
305 |
306 | line = in.readLine();
307 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.OVERFLOW_SINGLE_QUOTE));
308 |
309 | line = in.readLine();
310 | assertEquals(35, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END));
311 |
312 | line = in.readLine();
313 | assertEquals(16, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END));
314 | }
315 |
316 | /**
317 | * Test if separators inside multi-line comments get ignored. See MSQL-67
318 | *
319 | * @throws Exception
320 | */
321 | public void testSemicolonInComment() throws Exception {
322 | // @formatter:off
323 | String sql =
324 | "/* this is a commented-out statment:\n" + " SELECT * FROM TABLE;\n" + "and here the comment ends */ ";
325 | // @formatter:on
326 |
327 | BufferedReader in = new BufferedReader(new StringReader(sql));
328 |
329 | // Only checking if this complex statement can be parsed
330 | String line;
331 |
332 | line = in.readLine();
333 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.NO_END));
334 |
335 | line = in.readLine();
336 | assertEquals(SqlSplitter.OVERFLOW_COMMENT, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.OVERFLOW_COMMENT));
337 |
338 | line = in.readLine();
339 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, ";", SqlSplitter.OVERFLOW_COMMENT));
340 | }
341 |
342 | public void testSlashDelimiterWithComment() throws Exception {
343 |
344 | String sql = "begin\n/* this is a comment */\n SELECT * FROM TABLE;\nend;\n/\n";
345 |
346 | BufferedReader in = new BufferedReader(new StringReader(sql));
347 |
348 | // Only checking if this complex statement can be parsed
349 | String line;
350 |
351 | line = in.readLine();
352 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, "/", SqlSplitter.NO_END));
353 |
354 | line = in.readLine();
355 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, "/", SqlSplitter.NO_END));
356 |
357 | line = in.readLine();
358 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, "/", SqlSplitter.NO_END));
359 |
360 | line = in.readLine();
361 | assertEquals(SqlSplitter.NO_END, SqlSplitter.containsSqlEnd(line, "/", SqlSplitter.NO_END));
362 |
363 | line = in.readLine();
364 | assertEquals(1, SqlSplitter.containsSqlEnd(line, "/", SqlSplitter.NO_END));
365 | }
366 |
367 | private void contains(String sql, int expectedIndex) throws Exception {
368 | contains(sql, ";", expectedIndex);
369 | }
370 |
371 | private void containsNot(String sql) throws Exception {
372 | containsNot(sql, ";");
373 | }
374 |
375 | private void contains(String sql, String delimiter, int expectedIndex) throws Exception {
376 | assertEquals(sql, expectedIndex, SqlSplitter.containsSqlEnd(sql, delimiter, SqlSplitter.NO_END));
377 | }
378 |
379 | private void containsNot(String sql, String delimiter) throws Exception {
380 | assertTrue(sql, SqlSplitter.containsSqlEnd(sql, delimiter, SqlSplitter.NO_END) == SqlSplitter.NO_END);
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/src/test/java/org/codehaus/mojo/sql/SqlExecMojoTest.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Copyright 2006 The Codehaus
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 | * in compliance with the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software distributed under the License
12 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | import java.io.File;
18 | import java.nio.charset.StandardCharsets;
19 | import java.nio.file.Files;
20 | import java.sql.Connection;
21 | import java.sql.Statement;
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import java.util.Properties;
25 | import java.util.UUID;
26 |
27 | import org.apache.maven.plugin.MojoExecutionException;
28 | import org.apache.maven.plugin.testing.AbstractMojoTestCase;
29 | import org.apache.maven.plugin.testing.stubs.MavenProjectStub;
30 | import org.apache.maven.project.MavenProject;
31 | import org.apache.maven.settings.Server;
32 | import org.apache.maven.settings.Settings;
33 | import org.apache.maven.shared.filtering.MavenFileFilter;
34 | import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
35 | import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
36 |
37 | /**
38 | * Unit test for simple SqlExecMojo.
39 | */
40 | public class SqlExecMojoTest extends AbstractMojoTestCase {
41 |
42 | // jdbc:hsqldb:mem:egdb;sql.enforce_size=false
43 | // Group 1 ([^;]+) : "jdbc:hsqldb:mem:egdb"
44 | // Group 2 (;.*)? : ";sql.enforce_size=false"
45 | private static final String URL_REGEXP = "([^;]+)(;.*)?";
46 |
47 | private Properties p;
48 |
49 | public void setUp() throws Exception {
50 | super.setUp();
51 | p = new Properties();
52 | p.load(getClass().getResourceAsStream("/test.properties"));
53 | }
54 |
55 | /**
56 | * No error when there is no input
57 | */
58 | public void testNoCommandMojo() throws MojoExecutionException {
59 | SqlExecMojo mojo = createMojo();
60 | mojo.execute();
61 |
62 | assertEquals(0, mojo.getSuccessfulStatements());
63 | }
64 |
65 | public void testCreateCommandMojo() throws MojoExecutionException {
66 | SqlExecMojo mojo = createMojo();
67 | String command =
68 | "create table CREATE_COMMAND ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
69 | mojo.addText(command);
70 | mojo.execute();
71 |
72 | assertEquals(1, mojo.getSuccessfulStatements());
73 | }
74 |
75 | public void testDropCommandMojo() throws MojoExecutionException {
76 | SqlExecMojo mojo = createMojo();
77 | mojo.setSqlCommand("create table DROP_TEST (id integer)");
78 | mojo.execute();
79 |
80 | mojo.clear();
81 | mojo.addText("drop table DROP_TEST");
82 | mojo.execute();
83 |
84 | assertEquals(1, mojo.getSuccessfulStatements());
85 | }
86 |
87 | public void testFileSetMojo() throws MojoExecutionException {
88 |
89 | Fileset ds = new Fileset();
90 | ds.setBasedir("src/test");
91 | ds.setIncludes(new String[] {"**/create*.sql"});
92 | ds.scan();
93 | assert (ds.getIncludedFiles().length == 1);
94 |
95 | SqlExecMojo mojo = createMojo();
96 | mojo.setFileset(ds);
97 |
98 | mojo.execute();
99 |
100 | assertEquals(3, mojo.getSuccessfulStatements());
101 | }
102 |
103 | public void testFileArrayMojo() throws MojoExecutionException {
104 | File[] srcFiles = new File[2];
105 | srcFiles[0] = new File("src/test/data/create-test-tables.sql");
106 | srcFiles[1] = new File("src/test/data/drop-test-tables.sql");
107 |
108 | SqlExecMojo mojo = createMojo();
109 | mojo.setSrcFiles(srcFiles);
110 | mojo.execute();
111 |
112 | assertEquals(6, mojo.getSuccessfulStatements());
113 | }
114 |
115 | /**
116 | * Ensure srcFiles always execute first
117 | */
118 | public void testAllMojo() throws MojoExecutionException {
119 | SqlExecMojo mojo = createMojo();
120 | String command = "create table PERSON2 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
121 | mojo.addText(command);
122 |
123 | File[] srcFiles = new File[1];
124 | srcFiles[0] = new File("src/test/data/create-test-tables.sql");
125 | mojo.setSrcFiles(srcFiles);
126 |
127 | Fileset ds = new Fileset();
128 | ds.setBasedir("src/test");
129 | ds.setIncludes(new String[] {"**/drop*.sql"});
130 | ds.scan();
131 | mojo.setFileset(ds);
132 | mojo.execute();
133 |
134 | assertEquals(7, mojo.getSuccessfulStatements());
135 | }
136 |
137 | public void testOrderFile() throws MojoExecutionException {
138 | Fileset ds = new Fileset();
139 | ds.setBasedir("src/test");
140 | ds.setIncludes(new String[] {"**/drop*.sql", "**/create*.sql"});
141 | ds.scan();
142 |
143 | SqlExecMojo mojo = createMojo();
144 | mojo.setFileset(ds);
145 | mojo.setOrderFile(SqlExecMojo.FILE_SORTING_ASC);
146 | mojo.execute();
147 |
148 | assertEquals(6, mojo.getSuccessfulStatements());
149 |
150 | try {
151 | mojo.setOrderFile(SqlExecMojo.FILE_SORTING_DSC);
152 | mojo.execute();
153 | fail("Execution is not aborted on error.");
154 | } catch (MojoExecutionException e) {
155 | }
156 | }
157 |
158 | public void testOnErrorContinueMojo() throws MojoExecutionException {
159 | SqlExecMojo mojo = createMojo();
160 | String command = "create table BOGUS"; // bad syntax
161 | mojo.addText(command);
162 | mojo.setOnError("continue");
163 | mojo.execute();
164 | assertEquals(0, mojo.getSuccessfulStatements());
165 | }
166 |
167 | public void testOnErrorAbortMojo() throws MojoExecutionException {
168 | SqlExecMojo mojo = createMojo();
169 | String command = "create table BOGUS"; // bad syntax
170 | mojo.addText(command);
171 |
172 | try {
173 | mojo.execute();
174 | fail("Execution is not aborted on error.");
175 |
176 | } catch (MojoExecutionException e) {
177 |
178 | }
179 |
180 | assertEquals(0, mojo.getSuccessfulStatements());
181 | }
182 |
183 | public void testOnErrorAbortAfterMojo() throws MojoExecutionException {
184 | String commands = "create table BOGUS"; // bad syntax
185 |
186 | SqlExecMojo mojo = createMojo();
187 | mojo.addText(commands);
188 |
189 | File[] srcFiles = new File[1];
190 | srcFiles[0] = new File("src/test/data/invalid-syntax.sql");
191 |
192 | assertTrue(srcFiles[0].exists());
193 |
194 | mojo.setSrcFiles(srcFiles);
195 | mojo.setOnError("abortAfter");
196 |
197 | try {
198 | mojo.execute();
199 | fail("Execution is not aborted on error.");
200 |
201 | } catch (MojoExecutionException e) {
202 | // expected
203 | }
204 |
205 | assertEquals(0, mojo.getSuccessfulStatements());
206 | assertEquals(2, mojo.getTotalStatements());
207 | }
208 |
209 | public void testDefaultUsernamePassword() throws MojoExecutionException {
210 |
211 | Settings settings = new Settings();
212 | Server server = new Server();
213 | settings.addServer(server);
214 |
215 | SqlExecMojo mojo = createMojo(";user=;password=");
216 | mojo.setSettings(settings);
217 |
218 | // force a lookup of username
219 | mojo.setUsername(null);
220 | mojo.setPassword(null);
221 |
222 | mojo.execute();
223 |
224 | assertEquals("", mojo.getUsername());
225 | assertEquals("", mojo.getPassword());
226 | }
227 |
228 | public void testUsernamePasswordLookup() throws MojoExecutionException {
229 |
230 | Settings settings = new Settings();
231 | Server server = new Server();
232 | server.setId("somekey");
233 | server.setUsername("username");
234 | server.setPassword("password");
235 | settings.addServer(server);
236 |
237 | SqlExecMojo mojo = createMojo(";user=username;password=password");
238 | mojo.setSettings(settings);
239 |
240 | // force a lookup of username
241 | mojo.setSettingsKey("somekey");
242 | mojo.setUsername(null);
243 | mojo.setPassword(null);
244 |
245 | mojo.execute();
246 |
247 | assertEquals("username", mojo.getUsername());
248 | assertEquals("password", mojo.getPassword());
249 | }
250 |
251 | public void testBadDriver() {
252 | SqlExecMojo mojo = createMojo();
253 | mojo.setDriver("bad-driver");
254 | try {
255 | mojo.execute();
256 |
257 | fail("Bad driver is not detected");
258 | } catch (MojoExecutionException e) {
259 |
260 | }
261 | }
262 |
263 | public void testBadUrl() {
264 | SqlExecMojo mojo = createMojo();
265 | mojo.setUrl("bad-url");
266 | try {
267 | mojo.execute();
268 |
269 | fail("Bad URL is not detected");
270 | } catch (MojoExecutionException e) {
271 |
272 | }
273 | }
274 |
275 | public void testBadFile() {
276 | SqlExecMojo mojo = createMojo();
277 | File[] srcFiles = new File[1];
278 | srcFiles[0] = new File("a-every-bogus-file-that-does-not-exist");
279 |
280 | mojo.setSrcFiles(srcFiles);
281 | try {
282 | mojo.execute();
283 |
284 | fail("Bad files is not detected");
285 | } catch (MojoExecutionException e) {
286 |
287 | }
288 | }
289 |
290 | public void testOnError() {
291 | SqlExecMojo mojo = createMojo();
292 | mojo.setOnError("AbOrT");
293 | assertEquals(SqlExecMojo.ON_ERROR_ABORT, mojo.getOnError());
294 | mojo.setOnError("cOnTiNuE");
295 | assertEquals(SqlExecMojo.ON_ERROR_CONTINUE, mojo.getOnError());
296 | try {
297 | mojo.setOnError("bad");
298 | fail(IllegalArgumentException.class.getName() + " was not thrown.");
299 | } catch (IllegalArgumentException e) {
300 | // expected
301 | }
302 | try {
303 | mojo.setOnError(null);
304 | fail(IllegalArgumentException.class.getName() + " was not thrown.");
305 | } catch (IllegalArgumentException e) {
306 | // expected
307 | }
308 | }
309 |
310 | public void testSkip() throws MojoExecutionException {
311 | String command = "create table PERSON ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
312 | SqlExecMojo mojo = createMojo();
313 | mojo.addText(command);
314 | mojo.setSkip(true);
315 | mojo.execute();
316 |
317 | // no command was executed due to skip is on
318 | assertEquals(0, mojo.getSuccessfulStatements());
319 | }
320 |
321 | public void testDriverProperties() throws MojoExecutionException {
322 | SqlExecMojo mojo = createMojo();
323 | Properties driverProperties = mojo.getDriverProperties();
324 | assertEquals(2, driverProperties.size());
325 | assertEquals("value1", driverProperties.get("key1"));
326 | assertEquals("value2", driverProperties.get("key2"));
327 |
328 | mojo.setDriverProperties("key1=value1,key2");
329 | try {
330 | driverProperties = mojo.getDriverProperties();
331 | } catch (MojoExecutionException e) {
332 | }
333 | }
334 |
335 | public void testBlockMode() throws MojoExecutionException {
336 | String command = "create table BLOCKTABLE ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
337 |
338 | SqlExecMojo mojo = createMojo();
339 | mojo.addText(command);
340 | // TODO: Check if this is equal mojo.setEnableBlockMode( true );
341 | // to the following:
342 | mojo.setDelimiter(DelimiterType.ROW);
343 | mojo.execute();
344 | assertEquals(1, mojo.getSuccessfulStatements());
345 |
346 | mojo.setSqlCommand("");
347 | mojo.getTransactions().clear();
348 | command = "drop table BLOCKTABLE";
349 | mojo.addText(command);
350 | mojo.execute();
351 | assertEquals(1, mojo.getSuccessfulStatements());
352 | }
353 |
354 | public void testKeepFormat() throws MojoExecutionException {
355 | // Normally a line starting in -- would be ignored, but with keepformat mode
356 | // on it will not.
357 | String command = "--create table PERSON ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
358 |
359 | SqlExecMojo mojo = createMojo();
360 | mojo.addText(command);
361 | mojo.setKeepFormat(true);
362 |
363 | try {
364 | mojo.execute();
365 | fail("-- at the start of the SQL command is ignored.");
366 | } catch (MojoExecutionException e) {
367 | }
368 |
369 | assertEquals(0, mojo.getSuccessfulStatements());
370 | }
371 |
372 | public void testBadDelimiter() throws Exception {
373 | String command = "create table SEPARATOR ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50)):"
374 | + "create table SEPARATOR2 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
375 |
376 | SqlExecMojo mojo = createMojo();
377 | mojo.addText(command);
378 | mojo.setDelimiter(":");
379 |
380 | try {
381 | mojo.execute();
382 | fail("Expected parser error.");
383 | } catch (MojoExecutionException e) {
384 | }
385 | }
386 |
387 | public void testGoodDelimiter() throws Exception {
388 | String command = "create table SEPARATOR ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))\n:\n"
389 | + "create table SEPARATOR2 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
390 |
391 | SqlExecMojo mojo = createMojo();
392 | mojo.addText(command);
393 | mojo.setDelimiter(":");
394 |
395 | mojo.execute();
396 |
397 | assertEquals(2, mojo.getSuccessfulStatements());
398 | }
399 |
400 | public void testBadDelimiterType() throws Exception {
401 | String command =
402 | "create table BADDELIMTYPE ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))" + "\n:"
403 | + "create table BADDELIMTYPE2 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
404 |
405 | SqlExecMojo mojo = createMojo();
406 | mojo.addText(command);
407 | mojo.setDelimiter(":");
408 | mojo.setDelimiterType(DelimiterType.ROW);
409 |
410 | try {
411 | mojo.execute();
412 | fail("Expected parser error.");
413 | } catch (MojoExecutionException e) {
414 | }
415 | }
416 |
417 | public void testGoodDelimiterType() throws Exception {
418 | String command = "create table GOODDELIMTYPE ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))"
419 | + "\n: \n"
420 | + "create table GOODDELIMTYPE2 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
421 |
422 | SqlExecMojo mojo = createMojo();
423 | mojo.addText(command);
424 | mojo.setDelimiter(":");
425 | mojo.setDelimiterType(DelimiterType.NORMAL);
426 |
427 | mojo.execute();
428 | assertEquals(2, mojo.getSuccessfulStatements());
429 | }
430 |
431 | public void testOutputFile() throws Exception {
432 | String basedir = System.getProperty("basedir", ".");
433 | File outputFile = new File(basedir, "target/sql.out");
434 | outputFile.delete();
435 |
436 | SqlExecMojo mojo = createMojo();
437 | mojo.setOutputFile(outputFile);
438 | mojo.setPrintResultSet(true);
439 |
440 | String command =
441 | "create table GOODDELIMTYPE3 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50));";
442 | mojo.addText(command);
443 | mojo.execute();
444 |
445 | List list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
446 | assertEquals(1, list.size());
447 | assertEquals("0 rows affected", list.get(0));
448 |
449 | mojo.clear();
450 | mojo.setSqlCommand("insert into GOODDELIMTYPE3 (person_id) values (1)");
451 | mojo.execute();
452 |
453 | list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
454 | assertEquals(1, list.size());
455 | assertEquals("1 rows affected", list.get(0));
456 |
457 | mojo.clear();
458 | mojo.setSqlCommand("select * from GOODDELIMTYPE3");
459 | mojo.execute();
460 |
461 | list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
462 | assertEquals(4, list.size());
463 | assertEquals("PERSON_ID,FIRSTNAME,LASTNAME", list.get(0));
464 | assertEquals("1,null,null", list.get(1));
465 | assertEquals("", list.get(2));
466 | assertEquals("0 rows affected", list.get(3));
467 | }
468 |
469 | // MSQL-44
470 | public void testAnonymousBlock() throws MojoExecutionException {
471 | String command = "--/ Anonymous SQL Block\n"
472 | + "create table ANONBLOCKTABLE ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))\n"
473 | + "/\n"
474 | + "drop table ANONBLOCKTABLE";
475 |
476 | SqlExecMojo mojo = createMojo();
477 | mojo.setDelimiter("/");
478 | mojo.addText(command);
479 | mojo.execute();
480 | assertEquals(2, mojo.getSuccessfulStatements());
481 | }
482 |
483 | public void testSkipMissingFiles() throws MojoExecutionException {
484 | SqlExecMojo mojo = createMojo();
485 | mojo.setSkipMissingFiles(true);
486 | mojo.setSrcFiles(new File[] {
487 | new File("src/test/data/create-test-tables.sql"),
488 | new File("non/existing/file/path.sql"),
489 | new File("src/test/data/drop-test-tables.sql")
490 | });
491 | mojo.execute();
492 | assertEquals(6, mojo.getSuccessfulStatements());
493 | }
494 |
495 | public void testValidationSql() {
496 | SqlExecMojo mojo = createMojo();
497 | mojo.setConnectionValidationSqls(Arrays.asList("select 1 from nowhere"));
498 | try {
499 | mojo.execute();
500 |
501 | fail("Invalid connection is not detected");
502 | } catch (MojoExecutionException e) {
503 |
504 | }
505 |
506 | assertTrue(mojo.isConnectionClosed());
507 | }
508 |
509 | public void testConnectionRetryCountOnValidationError() {
510 | SqlExecMojo mojo = createMojo();
511 | mojo.setConnectionValidationSqls(Arrays.asList("select 1 from nowhere"));
512 | mojo.setConnectionRetryCount(2);
513 | try {
514 | mojo.execute();
515 |
516 | fail("Invalid connection is not detected");
517 | } catch (MojoExecutionException e) {
518 |
519 | }
520 |
521 | assertTrue(mojo.getConnectionRetryAttempts() >= 2);
522 | assertTrue(mojo.isConnectionClosed());
523 | }
524 |
525 | public void testConnectionRetryCountOnConnectionError() {
526 | SqlExecMojo mojo = createMojo();
527 | mojo.setUrl("bad-url");
528 | mojo.setConnectionRetryCount(2);
529 | try {
530 | mojo.execute();
531 |
532 | fail("Invalid connection is not detected");
533 | } catch (MojoExecutionException e) {
534 | }
535 |
536 | assertTrue(mojo.getConnectionRetryAttempts() >= 2);
537 | assertTrue(mojo.isConnectionClosed());
538 | }
539 |
540 | public void testConnectionRetryIntervalC2I1() {
541 | SqlExecMojo mojo = createMojo();
542 | mojo.setUrl("no-db-here");
543 | mojo.setConnectionRetryCount(2);
544 | mojo.setConnectionRetryInterval(1);
545 |
546 | long start = System.currentTimeMillis();
547 |
548 | try {
549 | mojo.execute();
550 | fail("Invalid connection is not detected");
551 | } catch (MojoExecutionException e) {
552 | long end = System.currentTimeMillis();
553 | assertTrue((end - start) >= 2000);
554 | }
555 | }
556 |
557 | public void testConnectionRetryIntervalC1I2() {
558 | SqlExecMojo mojo = createMojo();
559 | mojo.setUrl("no-db-here");
560 | mojo.setConnectionRetryCount(1);
561 | mojo.setConnectionRetryInterval(2);
562 |
563 | long start = System.currentTimeMillis();
564 |
565 | try {
566 | mojo.execute();
567 | fail("Invalid connection is not detected");
568 | } catch (MojoExecutionException e) {
569 | long end = System.currentTimeMillis();
570 | assertTrue((end - start) >= 2000);
571 | }
572 | }
573 |
574 | public void testConnectionRetryOnConnectionError() {
575 | SqlExecMojo mojo = createMojo();
576 | final String url = mojo.getUrl();
577 | mojo.setUrl("bad-url");
578 | mojo.setConnectionRetryCount(2);
579 | mojo.setConnectionRetryInterval(1);
580 |
581 | new Thread(new Runnable() {
582 | @Override
583 | public void run() {
584 | try {
585 | // Wait for three connection attempts
586 | Thread.sleep(500);
587 | } catch (InterruptedException ex) {
588 | // ignore
589 | }
590 | mojo.setUrl(url); // Url fixed, next connection should be success
591 | }
592 | })
593 | .start();
594 |
595 | try {
596 | mojo.execute();
597 | } catch (MojoExecutionException e) {
598 | fail("Connection was not restored: " + e.getMessage());
599 | }
600 |
601 | assertTrue(mojo.getConnectionRetryAttempts() >= 1);
602 | assertTrue(mojo.isConnectionClosed());
603 | }
604 |
605 | public void testConnectionRetryOnValidationError() {
606 | SqlExecMojo mojo = createMojo();
607 | mojo.addText("select * from test32");
608 | mojo.setConnectionValidationSqls(Arrays.asList("select 1 from test32"));
609 | mojo.setConnectionRetryCount(2);
610 | mojo.setConnectionRetryInterval(1);
611 |
612 | runSqlAfter(mojo, 1, Arrays.asList("create table test32 ( ID integer)"));
613 |
614 | try {
615 | mojo.execute();
616 | } catch (MojoExecutionException e) {
617 | fail("Connection was not restored: " + e.getMessage());
618 | }
619 |
620 | assertTrue(mojo.getConnectionRetryAttempts() >= 1);
621 | assertTrue(mojo.isConnectionClosed());
622 | }
623 |
624 | public void testConnectionRetryOnValidationErrorMultipleValidationSql() {
625 | SqlExecMojo mojo = createMojo();
626 | mojo.addText("select * from con_retry");
627 | mojo.setConnectionValidationSqls(Arrays.asList("select 1 from con_retry", "select 1 from con_retry2"));
628 | mojo.setConnectionRetryCount(3);
629 | mojo.setConnectionRetryInterval(1);
630 |
631 | runSqlAfter(mojo, 1, Arrays.asList("create table con_retry ( ID integer)"));
632 | runSqlAfter(mojo, 2, Arrays.asList("create table con_retry2 ( ID integer)"));
633 |
634 | try {
635 | mojo.execute();
636 |
637 | } catch (MojoExecutionException e) {
638 | fail("Connection was not restored: " + e.getMessage());
639 | }
640 |
641 | assertTrue(mojo.getConnectionRetryAttempts() >= 2);
642 | assertTrue(mojo.isConnectionClosed());
643 | }
644 |
645 | public void testValidationErrorMultipleValidationSql() {
646 | SqlExecMojo mojo = createMojo();
647 | mojo.addText("select * from valid_err");
648 | mojo.setConnectionValidationSqls(Arrays.asList("select 1 from valid_err", "select 1 from not"));
649 | mojo.setConnectionRetryCount(2);
650 | mojo.setConnectionRetryInterval(1);
651 |
652 | runSqlAfter(mojo, 1, Arrays.asList("create table valid_err ( ID integer)"));
653 |
654 | try {
655 | mojo.execute();
656 |
657 | fail("Connection was restored");
658 | } catch (MojoExecutionException e) {
659 | }
660 |
661 | assertTrue(mojo.getConnectionRetryAttempts() >= 2);
662 | assertTrue(mojo.isConnectionClosed());
663 | }
664 |
665 | public void testCustomPrintResultSet() throws Exception {
666 | CustomSqlExecMojo customMojo = new CustomSqlExecMojo();
667 | setUp(customMojo);
668 |
669 | String basedir = System.getProperty("basedir", ".");
670 | File outputFile = new File(basedir, "target/custom-print-resultset.out");
671 | outputFile.delete();
672 | customMojo.setOutputFile(outputFile);
673 | customMojo.setPrintResultSet(true);
674 |
675 | String command = "create table CUSTOM_PRINT ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
676 | customMojo.addText(command);
677 | customMojo.execute();
678 |
679 | List list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
680 | assertEquals(1, list.size());
681 | assertEquals("0 cows affected", list.get(0));
682 |
683 | customMojo.clear();
684 | customMojo.setSqlCommand("insert into CUSTOM_PRINT (person_id) values (1)");
685 | customMojo.execute();
686 |
687 | list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
688 | assertEquals(1, list.size());
689 | assertEquals("1 cows affected", list.get(0));
690 |
691 | customMojo.clear();
692 | customMojo.setSqlCommand("select * from CUSTOM_PRINT");
693 | customMojo.execute();
694 |
695 | list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
696 | assertEquals(2, list.size());
697 | assertEquals("This is the way", list.get(0));
698 | assertEquals("0 cows affected", list.get(1));
699 | }
700 |
701 | public void testShowFooter() throws Exception {
702 | String basedir = System.getProperty("basedir", ".");
703 | File outputFile = new File(basedir, "target/show-footer.out");
704 | outputFile.delete();
705 |
706 | SqlExecMojo mojo = createMojo();
707 | mojo.setShowFooter(false);
708 | mojo.setOutputFile(outputFile);
709 | mojo.setPrintResultSet(true);
710 |
711 | String command = "create table CUSTOM_PRINT ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50))";
712 | mojo.addText(command);
713 | mojo.execute();
714 |
715 | List list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
716 | assertEquals(0, list.size());
717 |
718 | mojo.clear();
719 | mojo.setSqlCommand("insert into CUSTOM_PRINT (person_id) values (1)");
720 | mojo.execute();
721 |
722 | list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
723 | assertEquals(0, list.size());
724 | }
725 |
726 | private SqlExecMojo createMojo(String dbUrlPostfix) {
727 | SqlExecMojo mojo = createMojo();
728 | mojo.setUrl(mojo.getUrl() + dbUrlPostfix);
729 | return mojo;
730 | }
731 |
732 | private SqlExecMojo createMojo() {
733 | SqlExecMojo mojo = new SqlExecMojo();
734 | setUp(mojo);
735 | return mojo;
736 | }
737 |
738 | private String toTestUrl(String url) {
739 | return url.replaceAll(URL_REGEXP, "$1" + UUID.randomUUID().toString() + "$2");
740 | }
741 |
742 | private void setUp(SqlExecMojo mojoToSetup) {
743 |
744 | // populate parameters
745 | mojoToSetup.setDriver(p.getProperty("driver"));
746 | mojoToSetup.setUsername(p.getProperty("user"));
747 | mojoToSetup.setPassword(p.getProperty("pasword"));
748 | mojoToSetup.setUrl(toTestUrl(p.getProperty("url")));
749 | mojoToSetup.setDriverProperties(p.getProperty("driverProperties"));
750 | mojoToSetup.setSqlCommand(null);
751 | mojoToSetup.setDelimiter(
752 | SqlExecMojo.DEFAULT_DELIMITER); // This will simulate the defaultValue of @Parameter (...)
753 | mojoToSetup.setOnError(SqlExecMojo.ON_ERROR_ABORT);
754 | mojoToSetup.setDelimiterType(DelimiterType.NORMAL);
755 | mojoToSetup.setEscapeProcessing(true);
756 | mojoToSetup.setOutputDelimiter(",");
757 | mojoToSetup.setShowFooter(true);
758 | mojoToSetup.setShowHeaders(true);
759 |
760 | try {
761 | MavenFileFilter filter =
762 | (MavenFileFilter) lookup("org.apache.maven.shared.filtering.MavenFileFilter", "default");
763 | mojoToSetup.setFileFilter(filter);
764 |
765 | SecDispatcher securityDispatcher =
766 | (SecDispatcher) lookup("org.sonatype.plexus.components.sec.dispatcher.SecDispatcher", "default");
767 | mojoToSetup.setSecurityDispatcher(securityDispatcher);
768 |
769 | MavenProject project = new MavenProjectStub();
770 | setVariableValueToObject(mojoToSetup, "project", project);
771 | } catch (ComponentLookupException | IllegalAccessException e) {
772 | throw new RuntimeException("Failed to setup mojo: " + e.getMessage(), e);
773 | }
774 | }
775 |
776 | public void testOutputFileEncoding() throws Exception {
777 | String command = "create table ENCODING ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50));\n"
778 | + "insert into ENCODING (PERSON_ID, FIRSTNAME, LASTNAME) values (1, 'A', 'B');";
779 |
780 | SqlExecMojo mojo = createMojo();
781 | mojo.addText(command);
782 |
783 | String basedir = System.getProperty("basedir", ".");
784 | File outputFile = new File(basedir, "target/sql-encoding.out");
785 | outputFile.delete();
786 | mojo.setOutputFile(outputFile);
787 | mojo.setPrintResultSet(true);
788 |
789 | mojo.execute();
790 |
791 | assertTrue("Output file: " + outputFile + " not found.", outputFile.exists());
792 | assertTrue("Unexpected empty output file. ", outputFile.length() > 0);
793 |
794 | List list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_8);
795 | assertTrue(list.size() == 2);
796 | assertEquals("0 rows affected", list.get(0));
797 | assertEquals("1 rows affected", list.get(1));
798 | }
799 |
800 | public void testOutputFileEncodingUtf16() throws Exception {
801 | String command =
802 | "create table ENCODING_UTF_16 ( PERSON_ID integer, FIRSTNAME varchar(50), LASTNAME varchar(50));\n"
803 | + "insert into ENCODING_UTF_16 (PERSON_ID, FIRSTNAME, LASTNAME) values (1, 'A', 'B');";
804 |
805 | SqlExecMojo mojo = createMojo();
806 | mojo.addText(command);
807 |
808 | String basedir = System.getProperty("basedir", ".");
809 | File outputFile = new File(basedir, "target/sql-encoding-utf-16.out");
810 | outputFile.delete();
811 | mojo.setOutputFile(outputFile);
812 | mojo.setOutputEncoding("UTF-16");
813 | mojo.setPrintResultSet(true);
814 |
815 | mojo.execute();
816 |
817 | assertTrue("Output file: " + outputFile + " not found.", outputFile.exists());
818 | assertTrue("Unexpected empty output file. ", outputFile.length() > 0);
819 |
820 | List list = Files.readAllLines(outputFile.toPath(), StandardCharsets.UTF_16);
821 | assertTrue(list.size() == 2);
822 | assertEquals("0 rows affected", list.get(0));
823 | assertEquals("1 rows affected", list.get(1));
824 | }
825 |
826 | public void testMysqlDelimiter() throws MojoExecutionException {
827 | File[] srcFiles = new File[4];
828 | srcFiles[0] = new File("src/test/data/delimiter-a.sql");
829 | srcFiles[1] = new File("src/test/data/delimiter-b.sql");
830 | srcFiles[2] = new File("src/test/data/delimiter-c.sql");
831 | srcFiles[3] = new File("src/test/data/create-test-tables.sql");
832 |
833 | SqlExecMojo mojo = createMojo();
834 | mojo.setSrcFiles(srcFiles);
835 | mojo.execute();
836 |
837 | assertEquals(9, mojo.getSuccessfulStatements());
838 | }
839 |
840 | private void runSqlAfter(SqlExecMojo mojo, int secs, final List sqls) {
841 | new Thread(new Runnable() {
842 | @Override
843 | public void run() {
844 | try {
845 | // Wait for three connection attempts
846 | Thread.sleep(secs * 1000);
847 | } catch (InterruptedException ex) {
848 | }
849 |
850 | Connection conn = null;
851 |
852 | try {
853 | conn = mojo.getConnection();
854 |
855 | try (Statement stmt = conn.createStatement()) {
856 | for (String sql : sqls) {
857 | stmt.execute(sql);
858 | }
859 | }
860 | } catch (Exception e) {
861 | fail(e.getMessage());
862 | } finally {
863 | mojo.closeConnection();
864 | }
865 | }
866 | })
867 | .start();
868 | }
869 | }
870 |
--------------------------------------------------------------------------------
/src/main/java/org/codehaus/mojo/sql/SqlExecMojo.java:
--------------------------------------------------------------------------------
1 | package org.codehaus.mojo.sql;
2 |
3 | /*
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | import java.io.BufferedOutputStream;
23 | import java.io.BufferedReader;
24 | import java.io.File;
25 | import java.io.FileOutputStream;
26 | import java.io.FileReader;
27 | import java.io.IOException;
28 | import java.io.InputStreamReader;
29 | import java.io.PrintStream;
30 | import java.io.Reader;
31 | import java.io.StringReader;
32 | import java.nio.file.Files;
33 | import java.sql.Connection;
34 | import java.sql.Driver;
35 | import java.sql.ResultSet;
36 | import java.sql.ResultSetMetaData;
37 | import java.sql.SQLException;
38 | import java.sql.SQLWarning;
39 | import java.sql.Statement;
40 | import java.util.Collections;
41 | import java.util.HashMap;
42 | import java.util.List;
43 | import java.util.Map;
44 | import java.util.Properties;
45 | import java.util.StringTokenizer;
46 | import java.util.Vector;
47 |
48 | import org.apache.commons.text.StringEscapeUtils;
49 | import org.apache.maven.execution.MavenSession;
50 | import org.apache.maven.plugin.AbstractMojo;
51 | import org.apache.maven.plugin.MojoExecutionException;
52 | import org.apache.maven.plugins.annotations.Component;
53 | import org.apache.maven.plugins.annotations.Mojo;
54 | import org.apache.maven.plugins.annotations.Parameter;
55 | import org.apache.maven.project.MavenProject;
56 | import org.apache.maven.settings.Server;
57 | import org.apache.maven.settings.Settings;
58 | import org.apache.maven.shared.filtering.MavenFileFilter;
59 | import org.apache.maven.shared.filtering.MavenFileFilterRequest;
60 | import org.apache.maven.shared.filtering.MavenFilteringException;
61 | import org.apache.maven.shared.scriptinterpreter.ScriptException;
62 | import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
63 | import org.codehaus.plexus.util.FileUtils;
64 | import org.codehaus.plexus.util.IOUtil;
65 | import org.codehaus.plexus.util.StringUtils;
66 | import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
67 | import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
68 |
69 | /**
70 | * Executes SQL against a database.
71 | */
72 | @Mojo(name = "execute", requiresProject = true, threadSafe = true)
73 | public class SqlExecMojo extends AbstractMojo {
74 |
75 | private static final String DELIMITER_STATEMENT = "DELIMITER ";
76 | private static final int DELIMITER_STATEMENT_LENGTH = DELIMITER_STATEMENT.length();
77 |
78 | /**
79 | * Call {@link #setOnError(String)} with this value to abort SQL command execution if an error is found.
80 | */
81 | public static final String ON_ERROR_ABORT = "abort";
82 |
83 | /**
84 | * Call {@link #setOnError(String)} with this value to continue SQL command execution until all commands have been
85 | * attempted, then abort the build if an SQL error occurred in any of the commands.
86 | */
87 | public static final String ON_ERROR_ABORT_AFTER = "abortAfter";
88 |
89 | /**
90 | * Call {@link #setOnError(String)} with this value to continue SQL command execution if an error is found.
91 | */
92 | public static final String ON_ERROR_CONTINUE = "continue";
93 |
94 | /**
95 | * Call {@link #setOrderFile(String)} with this value to sort in ascendant order the sql files.
96 | */
97 | public static final String FILE_SORTING_ASC = "ascending";
98 |
99 | /**
100 | * Call {@link #setOrderFile(String)} with this value to sort in descendant order the sql files.
101 | */
102 | public static final String FILE_SORTING_DSC = "descending";
103 |
104 | /**
105 | * The default SQL delimiter which is used to separate statements.
106 | */
107 | public static final String DEFAULT_DELIMITER = ";";
108 |
109 | //////////////////////////// User Info ///////////////////////////////////
110 |
111 | /**
112 | * Database username. If not given, it will be looked up through settings.xml's server with
113 | * ${settingsKey} as key.
114 | *
115 | * @since 1.0
116 | */
117 | @Parameter(property = "username")
118 | private String username;
119 |
120 | /**
121 | * Database password. If not given, it will be looked up through settings.xml's server with
122 | * ${settingsKey} as key.
123 | *
124 | * @since 1.0
125 | */
126 | @Parameter(property = "password")
127 | private String password;
128 |
129 | /**
130 | * Ignore the password and use anonymous access. This may be useful for databases like MySQL which do not allow
131 | * empty password parameters in the connection initialization.
132 | *
133 | * @since 1.4
134 | */
135 | @Parameter(defaultValue = "false")
136 | private boolean enableAnonymousPassword;
137 |
138 | /**
139 | * Additional key=value pairs separated by comma to be passed into JDBC driver.
140 | *
141 | * @since 1.0
142 | */
143 | @Parameter(defaultValue = "", property = "driverProperties")
144 | private String driverProperties;
145 |
146 | /**
147 | * @since 1.0
148 | */
149 | @Parameter(defaultValue = "${settings}", readonly = true, required = true)
150 | private Settings settings;
151 |
152 | /**
153 | * Server's id in settings.xml to look up username and password. Defaults to
154 | * ${url} if not given.
155 | *
156 | * @since 1.0
157 | */
158 | @Parameter(property = "settingsKey")
159 | private String settingsKey;
160 |
161 | /**
162 | * MNG-4384
163 | *
164 | * @since 1.5
165 | */
166 | @Component(role = SecDispatcher.class, hint = "default")
167 | private SecDispatcher securityDispatcher;
168 |
169 | /**
170 | * Skip execution when there is an error obtaining a connection. This is a special case to support databases, such
171 | * as embedded Derby, that can shutdown the database via the URL (i.e. shutdown=true).
172 | *
173 | * @since 1.1
174 | */
175 | @Parameter(defaultValue = "false", property = "skipOnConnectionError")
176 | private boolean skipOnConnectionError;
177 |
178 | /**
179 | * Skip missing files defined by {@link #setSrcFiles(File[])}. This behavior allows to define a full fledged list
180 | * of all sql files in a single {@code pom.xml} without failing if used by modules for which some sql files are not
181 | * available on the classpath.
182 | */
183 | @Parameter(defaultValue = "false", property = "skipMissingFiles")
184 | private boolean skipMissingFiles;
185 |
186 | /**
187 | * Setting this parameter to true will force the execution of this mojo, even if it would get skipped
188 | * usually.
189 | */
190 | @Parameter(defaultValue = "false", property = "forceOpenJpaExecution", required = true)
191 | private boolean forceMojoExecution;
192 |
193 | /**
194 | * The Maven Project Object
195 | */
196 | @Parameter(defaultValue = "${project}", readonly = true, required = true)
197 | private MavenProject project;
198 |
199 | /**
200 | */
201 | @Parameter(defaultValue = "${session}", readonly = true, required = true)
202 | private MavenSession mavenSession;
203 |
204 | //////////////////////////////// Source info /////////////////////////////
205 |
206 | /**
207 | * SQL input commands separated by ${delimiter}.
208 | *
209 | * @since 1.0
210 | */
211 | @Parameter(property = "sqlCommand")
212 | private String sqlCommand;
213 |
214 | /**
215 | * List of files containing SQL statements to load.
216 | *
217 | * @since 1.0
218 | * @see #fileset
219 | */
220 | @Parameter
221 | private File[] srcFiles;
222 |
223 | /**
224 | * File(s) containing SQL statements to load. Only use a Fileset if you want to use ant-like filepatterns, otherwise
225 | * use srcFiles. The order is based on a matching occurrence while scanning the directory (not the order of
226 | * includes!).
227 | *
228 | * @since 1.0
229 | * @see #srcFiles
230 | */
231 | @Parameter
232 | private Fileset fileset;
233 |
234 | /**
235 | * When true, skip the execution.
236 | *
237 | * @since 1.0
238 | */
239 | @Parameter(defaultValue = "false")
240 | private boolean skip;
241 |
242 | ////////////////////////////////// Database info /////////////////////////
243 | /**
244 | * Database URL.
245 | *
246 | * @since 1.0-beta-1
247 | */
248 | @Parameter(property = "url", required = true)
249 | private String url;
250 |
251 | /**
252 | * Database driver classname.
253 | *
254 | * @since 1.0
255 | */
256 | @Parameter(property = "driver", required = true)
257 | private String driver;
258 |
259 | ////////////////////////////// Operation Configuration ////////////////////
260 | /**
261 | * Set to true to execute none-transactional SQL.
262 | *
263 | * @since 1.0
264 | */
265 | @Parameter(defaultValue = "false", property = "autocommit")
266 | private boolean autocommit;
267 |
268 | /**
269 | * Set to true to execute immediately rollback transactional SQL.
270 | *
271 | * @since 3.0.0
272 | */
273 | @Parameter(defaultValue = "false", property = "rollbackTransactions")
274 | private boolean rollbackTransactions;
275 |
276 | /**
277 | * Action to perform if an error is found. Possible values are abort and continue.
278 | *
279 | * @since 1.0
280 | */
281 | @Parameter(defaultValue = ON_ERROR_ABORT, property = "onError")
282 | private String onError;
283 |
284 | /**
285 | * List of connection validation SQLs. If the value is given, the SQL files
286 | * are processed after all validation have been executed successfully.
287 | * The validation SQLs can be used to verify that required tables or data
288 | * exists before the SQLs are processed.
289 | *
290 | * @since 3.0.0
291 | */
292 | @Parameter(defaultValue = "", property = "connectionValidationSqls")
293 | private List connectionValidationSqls;
294 |
295 | /**
296 | * Connection retry count. How many times connection is tried to be
297 | * opened and validated after failure.
298 | *
299 | * @since 3.0.0
300 | */
301 | @Parameter(defaultValue = "0", property = "connectionRetryCount")
302 | private int connectionRetryCount;
303 |
304 | /**
305 | * How many seconds are waited before next connection attempt.
306 | *
307 | * @since 3.0.0
308 | */
309 | @Parameter(defaultValue = "1", property = "connectionRetryInterval")
310 | private int connectionRetryInterval = 1;
311 |
312 | ////////////////////////////// Parser Configuration ////////////////////
313 |
314 | /**
315 | * Set the delimiter that separates SQL statements.
316 | *
317 | * @since 1.0
318 | */
319 | @Parameter(defaultValue = DEFAULT_DELIMITER, property = "delimiter")
320 | private String delimiter;
321 |
322 | /**
323 | *
324 | * The delimiter type takes two values - "normal" and "row". Normal means that any occurrence of the delimiter
325 | * terminate the SQL command whereas with row, only a line containing just the delimiter is recognized as the end of
326 | * the command.
327 | *
328 | *
329 | * For example, set this to "go" and delimiterType to "row" for Sybase ASE or MS SQL Server.
330 | *
331 | *
332 | * @since 1.2
333 | */
334 | @Parameter(defaultValue = DelimiterType.NORMAL, property = "delimiterType")
335 | private String delimiterType;
336 |
337 | /**
338 | * Set the order in which the SQL files will be executed. Possible values are ascending and
339 | * descending. Any other value means that no sorting will be performed. Refers to {@link #fileset} and
340 | * {@link #srcFiles}
341 | *
342 | * @since 1.1
343 | */
344 | @Parameter(property = "orderFile")
345 | private String orderFile;
346 |
347 | /**
348 | * Keep the format of an SQL block.
349 | *
350 | * @since 1.1
351 | */
352 | @Parameter(defaultValue = "false", property = "keepFormat")
353 | private boolean keepFormat;
354 |
355 | ///////////////////////////////////////////////////////////////////////////////////////
356 | /**
357 | * Print SQL results.
358 | *
359 | * @since 1.3
360 | */
361 | @Parameter(defaultValue = "false", property = "printResultSet")
362 | private boolean printResultSet;
363 |
364 | /**
365 | * Print header columns.
366 | */
367 | @Parameter(property = "showheaders", defaultValue = "true")
368 | private boolean showHeaders;
369 |
370 | /**
371 | * Print footer value, informing on the number of rows
372 | */
373 | @Parameter(property = "showFooter", defaultValue = "true")
374 | private boolean showFooter;
375 |
376 | /**
377 | * Dump the SQL execution's output to a file.
378 | * Default value is: System.out.
379 | *
380 | * @since 1.3
381 | */
382 | @Parameter
383 | private File outputFile;
384 |
385 | /**
386 | * The delimiter used to separate fields in the output when using printResultSet.
387 | *
388 | * @since 1.4
389 | */
390 | @Parameter(defaultValue = ",")
391 | private String outputDelimiter;
392 |
393 | /**
394 | * Encoding to use while writing queried data to a file.
395 | *
396 | * @since 3.0.0
397 | */
398 | @Parameter(defaultValue = "${project.build.sourceEncoding}", property = "outputEncoding")
399 | private String outputEncoding;
400 |
401 | /**
402 | * Encoding to use when reading SQL statements from a file.
403 | *
404 | * @since 1.1
405 | */
406 | @Parameter(defaultValue = "${project.build.sourceEncoding}", property = "encoding")
407 | private String encoding;
408 |
409 | /**
410 | * Append to an existing file or overwrite it?
411 | */
412 | private boolean append = false;
413 |
414 | /**
415 | * Argument to Statement.setEscapeProcessing If you want the driver to use regular SQL syntax then set this to
416 | * false.
417 | *
418 | * @since 1.4
419 | */
420 | @Parameter(defaultValue = "true", property = "escapeProcessing")
421 | private boolean escapeProcessing;
422 |
423 | ////////////////////////////////// Internal properties//////////////////////
424 |
425 | /**
426 | * number of successful executed statements
427 | */
428 | private int successfulStatements = 0;
429 |
430 | /**
431 | * number of total executed statements
432 | */
433 | private int totalStatements = 0;
434 |
435 | /**
436 | * Database connection
437 | */
438 | private Connection conn = null;
439 |
440 | /**
441 | * SQL statement
442 | */
443 | private Statement statement = null;
444 |
445 | /**
446 | * SQL transactions to perform
447 | */
448 | private List transactions = new Vector<>();
449 |
450 | /**
451 | * @since 1.4
452 | */
453 | @Component(role = MavenFileFilter.class)
454 | private MavenFileFilter fileFilter;
455 |
456 | /**
457 | * Set to true if you want to filter the srcFiles using system-, user- and project properties
458 | *
459 | * @since 1.4
460 | */
461 | @Parameter(defaultValue = "false", property = "enableFiltering")
462 | private boolean enableFiltering;
463 |
464 | private ScriptRunner scriptRunner;
465 |
466 | /**
467 | * @since 1.6
468 | */
469 | @Parameter
470 | private File preExecuteHookScript;
471 |
472 | /**
473 | * @since 1.6
474 | */
475 | @Parameter
476 | private File postExecuteHookScript;
477 |
478 | /**
479 | * number of done connection retry attempts
480 | *
481 | * @since 3.0.0
482 | */
483 | private int connectionRetryAttempts;
484 |
485 | /**
486 | * Add a SQL transaction to execute
487 | *
488 | * @return a new SqlExecMojo.Transaction
489 | */
490 | public Transaction createTransaction() {
491 | Transaction t = new Transaction();
492 | transactions.add(t);
493 | return t;
494 | }
495 |
496 | /**
497 | * Set an inline SQL command to execute. NB: Properties are not expanded in this text.
498 | *
499 | * @param sql the sql statement to add
500 | */
501 | public void addText(String sql) {
502 | if (this.sqlCommand == null) {
503 | this.sqlCommand = sql;
504 | } else {
505 | this.sqlCommand += sql;
506 | }
507 | }
508 |
509 | /**
510 | * Set the file encoding to use on the SQL files read in
511 | *
512 | * @param encoding the encoding to use on the files
513 | */
514 | public void setEncoding(String encoding) {
515 | this.encoding = encoding;
516 | }
517 |
518 | /**
519 | * Set the delimiter that separates SQL statements. Defaults to ";";
520 | *
521 | * @param delimiter the new delimiter
522 | */
523 | public void setDelimiter(String delimiter) {
524 | this.delimiter = delimiter;
525 | }
526 |
527 | /**
528 | * Set the delimiter type: "normal" or "row" (default "normal").
529 | *
530 | * @param delimiterType the new delimiterType
531 | */
532 | public void setDelimiterType(String delimiterType) {
533 | this.delimiterType = delimiterType;
534 | }
535 |
536 | /**
537 | * Print result sets from the statements; optional, default false
538 | *
539 | * @param print true to print the resultset, otherwise false
540 | */
541 | public void setPrintResultSet(boolean print) {
542 | this.printResultSet = print;
543 | }
544 |
545 | /**
546 | * Print headers for result sets from the statements; optional, default true.
547 | *
548 | * @param showHeaders true to show the headers, otherwise false
549 | */
550 | public void setShowHeaders(boolean showHeaders) {
551 | this.showHeaders = showHeaders;
552 | }
553 |
554 | /**
555 | * Set the output file;
556 | *
557 | * @param output the output file
558 | */
559 | public void setOutputFile(File output) {
560 | this.outputFile = output;
561 | }
562 |
563 | /**
564 | * whether output should be appended to or overwrite an existing file. Defaults to false.
565 | *
566 | * @param append true to append, otherwise false to overwrite
567 | */
568 | public void setAppend(boolean append) {
569 | this.append = append;
570 | }
571 |
572 | /**
573 | * whether or not format should be preserved. Defaults to false.
574 | *
575 | * @param keepformat {@code true} to keep the format {@code false} otherwise.
576 | */
577 | public void setKeepFormat(boolean keepformat) {
578 | this.keepFormat = keepformat;
579 | }
580 |
581 | /**
582 | * Set escape processing for statements.
583 | *
584 | * @param enable true to escape, otherwise false
585 | */
586 | public void setEscapeProcessing(boolean enable) {
587 | escapeProcessing = enable;
588 | }
589 |
590 | /**
591 | *
592 | * Determine if the mojo execution should get skipped.
593 | *
594 | * This is the case if:
595 | *
596 | * - {@link #skip} is
true
597 | * - if the mojo gets executed on a project with packaging type 'pom' and {@link #forceMojoExecution} is
598 | *
false
599 | *
600 | *
601 | * @return true if the mojo execution should be skipped.
602 | */
603 | protected boolean skipMojo() {
604 | if (skip) {
605 | getLog().info("User has requested to skip execution.");
606 | return true;
607 | }
608 |
609 | if (!forceMojoExecution && project != null && "pom".equals(project.getPackaging())) {
610 | getLog().info("Skipping sql execution for project with packaging type 'pom'");
611 | return true;
612 | }
613 |
614 | return false;
615 | }
616 |
617 | /**
618 | * Load the sql file and then execute it
619 | *
620 | * @throws MojoExecutionException
621 | */
622 | @Override
623 | public void execute() throws MojoExecutionException {
624 |
625 | if (skipMojo()) {
626 | return;
627 | }
628 |
629 | executeScriptPlusSql();
630 | }
631 |
632 | private void executeScriptPlusSql() throws MojoExecutionException {
633 | // prepare scriptrunner
634 | scriptRunner = new ScriptRunner();
635 | scriptRunner.setScriptEncoding(encoding);
636 | // scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath );
637 | // scriptRunner.setClassPath( scriptClassPath );
638 |
639 | Map context = new HashMap<>();
640 |
641 | try {
642 | if (preExecuteHookScript != null) {
643 | scriptRunner.run("pre-execute script", preExecuteHookScript, context, null);
644 | }
645 |
646 | executeSqlCore();
647 | } catch (IOException | ScriptException e) {
648 | throw new MojoExecutionException(e.getMessage(), e);
649 | } finally {
650 | try {
651 | if (postExecuteHookScript != null) {
652 | scriptRunner.run("post-execute script", postExecuteHookScript, context, null);
653 | }
654 | } catch (IOException | ScriptException e) {
655 | throw new MojoExecutionException(e.getMessage(), e);
656 | }
657 | }
658 | }
659 |
660 | protected void executeSqlCore() throws MojoExecutionException {
661 | connectionRetryAttempts = 0;
662 | successfulStatements = 0;
663 | totalStatements = 0;
664 |
665 | // Connection is not valid until it is proved to be
666 | boolean connectionIsValid = false;
667 |
668 | loadUserInfoFromSettings();
669 |
670 | addCommandToTransactions();
671 |
672 | addFilesToTransactions();
673 |
674 | addFileSetToTransactions();
675 |
676 | sortTransactions();
677 |
678 | // Loop until connection is valid (or exited otherwise)
679 | while (!connectionIsValid) {
680 |
681 | // Check should we retry, if there are some errors
682 | boolean retryOnConnectionError = this.connectionRetryCount > connectionRetryAttempts;
683 |
684 | try {
685 | // Get a new connection if is not already open
686 | if (conn == null || conn.isClosed()) {
687 | conn = getConnection();
688 | }
689 |
690 | validateConnection(conn);
691 |
692 | // No SQLException thrown, the connection should be fine
693 | connectionIsValid = true;
694 | } catch (SQLException e) {
695 | if (retryOnConnectionError) {
696 | // User want to retry connection, increase the retry attempts
697 | connectionRetryAttempts++;
698 | getLog().info("Connection validation failed: retrying connection in " + connectionRetryInterval
699 | + " secs (" + connectionRetryAttempts + "/" + connectionRetryCount + ")...");
700 | waitToRetryConnectionValidation();
701 | } else if (!this.skipOnConnectionError) {
702 | // Make sure connection is closed, it can be open if only validation failed
703 | closeConnection();
704 |
705 | throw new MojoExecutionException(e.getMessage(), e);
706 | } else {
707 | // Make sure connection is closed, it can be open if only validation failed
708 | closeConnection();
709 |
710 | // Error on get connection and user asked to skip the rest
711 | return;
712 | }
713 | }
714 | }
715 |
716 | try {
717 | statement = conn.createStatement();
718 | statement.setEscapeProcessing(escapeProcessing);
719 |
720 | PrintStream out = System.out;
721 | try {
722 | if (outputFile != null) {
723 | getLog().debug("Opening PrintStream to output file " + outputFile);
724 | outputFile.getParentFile().mkdirs();
725 |
726 | if (StringUtils.isEmpty(this.outputEncoding)) {
727 | this.outputEncoding = System.getProperty("file.encoding");
728 | }
729 |
730 | out = new PrintStream(
731 | new BufferedOutputStream(new FileOutputStream(outputFile.getAbsolutePath(), append)),
732 | true,
733 | this.outputEncoding);
734 | }
735 |
736 | // Process all transactions
737 | for (Transaction t : transactions) {
738 | t.runTransaction(out);
739 |
740 | if (!autocommit) {
741 | if (rollbackTransactions) {
742 | getLog().debug("Rollback transaction");
743 | conn.rollback();
744 | } else {
745 | getLog().debug("Committing transaction");
746 | conn.commit();
747 | }
748 | }
749 | }
750 | } finally {
751 | if (out != null && out != System.out) {
752 | out.close();
753 | }
754 | }
755 | } catch (IOException e) {
756 | throw new MojoExecutionException(e.getMessage(), e);
757 | } catch (SQLException e) {
758 | if (!autocommit && conn != null && ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) {
759 | try {
760 | conn.rollback();
761 | } catch (SQLException ex) {
762 | // ignore
763 | }
764 | }
765 | throw new MojoExecutionException(e.getMessage(), e);
766 | } finally {
767 | closeStatement();
768 | closeConnection();
769 | }
770 |
771 | getLog().info(getSuccessfulStatements() + " of " + getTotalStatements()
772 | + " SQL statements executed successfully");
773 |
774 | if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(getOnError()) && totalStatements != successfulStatements) {
775 | throw new MojoExecutionException("Some SQL statements failed to execute");
776 | }
777 | }
778 |
779 | /**
780 | * Add sql command to transactions list.
781 | */
782 | private void addCommandToTransactions() {
783 | if (sqlCommand != null) {
784 | createTransaction().addText(sqlCommand.trim());
785 | }
786 | }
787 |
788 | /**
789 | * Add user sql fileset to transation list
790 | */
791 | private void addFileSetToTransactions() {
792 | String[] includedFiles;
793 | if (fileset != null) {
794 | fileset.scan();
795 | includedFiles = fileset.getIncludedFiles();
796 | } else {
797 | includedFiles = new String[0];
798 | }
799 |
800 | for (String includedFile : includedFiles) {
801 | createTransaction().setSrc(new File(fileset.getBasedir(), includedFile));
802 | }
803 | }
804 |
805 | /**
806 | * Add user input of srcFiles to transaction list.
807 | *
808 | * @throws MojoExecutionException
809 | */
810 | private void addFilesToTransactions() throws MojoExecutionException {
811 | File[] files = getSrcFiles();
812 |
813 | MavenFileFilterRequest request = new MavenFileFilterRequest();
814 | request.setEncoding(encoding);
815 | request.setMavenSession(mavenSession);
816 | request.setMavenProject(project);
817 | request.setFiltering(enableFiltering);
818 | if (files != null) {
819 | for (File sourceFile : files) {
820 | if (sourceFile != null && !sourceFile.exists()) {
821 | if (skipMissingFiles) {
822 | getLog().debug(String.format("ignoring non existing sql file [%s].", sourceFile.getPath()));
823 | continue;
824 | }
825 | throw new MojoExecutionException(sourceFile.getPath() + " not found.");
826 | }
827 |
828 | String basename = FileUtils.basename(sourceFile.getName());
829 | String extension = FileUtils.extension(sourceFile.getName());
830 | File targetFile = FileUtils.createTempFile(basename, extension, null);
831 | if (!getLog().isDebugEnabled()) {
832 | targetFile.deleteOnExit();
833 | }
834 |
835 | request.setFrom(sourceFile);
836 | request.setTo(targetFile);
837 |
838 | try {
839 | fileFilter.copyFile(request);
840 | } catch (MavenFilteringException e) {
841 | throw new MojoExecutionException(e.getMessage());
842 | }
843 |
844 | createTransaction().setSrc(targetFile);
845 | }
846 | }
847 | }
848 |
849 | /**
850 | * Sort the transaction list.
851 | */
852 | private void sortTransactions() {
853 | if (FILE_SORTING_ASC.equalsIgnoreCase(this.orderFile)) {
854 | Collections.sort(transactions);
855 | } else if (FILE_SORTING_DSC.equalsIgnoreCase(this.orderFile)) {
856 | transactions.sort(Collections.reverseOrder());
857 | }
858 | }
859 |
860 | /**
861 | * Load username password from settings if user has not set them in JVM properties
862 | *
863 | * @throws MojoExecutionException
864 | */
865 | private void loadUserInfoFromSettings() throws MojoExecutionException {
866 | if (this.settingsKey == null) {
867 | this.settingsKey = getUrl();
868 | }
869 |
870 | if ((getUsername() == null || getPassword() == null) && (settings != null)) {
871 | Server server = this.settings.getServer(this.settingsKey);
872 |
873 | if (server != null) {
874 | if (getUsername() == null) {
875 | setUsername(server.getUsername());
876 | }
877 |
878 | if (getPassword() == null && server.getPassword() != null) {
879 | try {
880 | setPassword(securityDispatcher.decrypt(server.getPassword()));
881 | } catch (SecDispatcherException e) {
882 | throw new MojoExecutionException(e.getMessage());
883 | }
884 | }
885 | }
886 | }
887 |
888 | if (getUsername() == null) {
889 | // allow empty username
890 | setUsername("");
891 | }
892 |
893 | if (getPassword() == null) {
894 | // allow empty password
895 | setPassword("");
896 | }
897 | }
898 |
899 | /**
900 | * Creates a new Connection as using the driver, url, userid and password specified. The calling method is
901 | * responsible for closing the connection.
902 | *
903 | * @return Connection the newly created connection.
904 | * @throws MojoExecutionException if the UserId/Password/Url is not set or there is no suitable driver or the driver
905 | * fails to load.
906 | * @throws SQLException if there is problem getting connection with valid url
907 | */
908 | Connection getConnection() throws MojoExecutionException, SQLException {
909 | getLog().debug("connecting to " + getUrl());
910 | Properties info = new Properties();
911 | info.put("user", getUsername());
912 |
913 | if (!enableAnonymousPassword) {
914 | info.put("password", getPassword());
915 | }
916 |
917 | info.putAll(this.getDriverProperties());
918 |
919 | Driver driverInstance;
920 |
921 | try {
922 | Class> dc = Class.forName(getDriver());
923 | driverInstance = (Driver) dc.getDeclaredConstructor().newInstance();
924 | } catch (ClassNotFoundException e) {
925 | throw new MojoExecutionException("Driver class not found: " + getDriver(), e);
926 | } catch (Exception e) {
927 | throw new MojoExecutionException("Failure loading driver: " + getDriver(), e);
928 | }
929 |
930 | Connection connection = driverInstance.connect(getUrl(), info);
931 |
932 | if (connection == null) {
933 | // Driver doesn't understand the URL
934 | throw new SQLException("No suitable Driver for " + getUrl());
935 | }
936 |
937 | connection.setAutoCommit(autocommit);
938 | return connection;
939 | }
940 |
941 | /**
942 | * parse driverProperties into Properties set
943 | *
944 | * @return the driver properties
945 | * @throws MojoExecutionException
946 | */
947 | protected Properties getDriverProperties() throws MojoExecutionException {
948 | Properties properties = new Properties();
949 |
950 | if (!StringUtils.isEmpty(this.driverProperties)) {
951 | String[] tokens = StringUtils.split(this.driverProperties, ",");
952 | for (String token : tokens) {
953 | String[] keyValueTokens = StringUtils.split(token.trim(), "=");
954 | if (keyValueTokens.length != 2) {
955 | throw new MojoExecutionException("Invalid JDBC Driver properties: " + this.driverProperties);
956 | }
957 |
958 | properties.setProperty(keyValueTokens[0], keyValueTokens[1]);
959 | }
960 | }
961 |
962 | return properties;
963 | }
964 |
965 | /**
966 | * read in lines and execute them
967 | *
968 | * @param reader the reader
969 | * @param out the outputstream
970 | * @throws SQLException
971 | * @throws IOException
972 | */
973 | private void runStatements(Reader reader, PrintStream out) throws SQLException, IOException {
974 | String line;
975 |
976 | // The delimeter can be overwritten by mysql DELIMETER command.
977 | // If the delimeter is overwritten it is in one file, not a global change
978 | String fileDelimeter = this.delimiter;
979 |
980 | // TODO: Check if this equivalent with if (enableBlockMode) {..
981 | if (delimiterType.equals(DelimiterType.ROW)) {
982 | // no need to parse the content, ship it directly to jdbc in one sql statement
983 | line = IOUtil.toString(reader);
984 | execSQL(line, out);
985 | return;
986 | }
987 |
988 | StringBuilder sql = new StringBuilder();
989 | BufferedReader in = new BufferedReader(reader);
990 | int overflow = SqlSplitter.NO_END;
991 |
992 | while ((line = in.readLine()) != null) {
993 | if (!keepFormat) {
994 | line = line.trim();
995 | }
996 |
997 | if (!keepFormat) {
998 | if (line.startsWith("//")) {
999 | continue;
1000 | }
1001 | if (line.startsWith("--")) {
1002 | continue;
1003 | }
1004 | StringTokenizer st = new StringTokenizer(line);
1005 | if (st.hasMoreTokens()) {
1006 | String token = st.nextToken();
1007 | if ("REM".equalsIgnoreCase(token)) {
1008 | continue;
1009 | }
1010 | }
1011 | }
1012 |
1013 | // Check for mysql delimiter statements
1014 | if (overflow >= SqlSplitter.NO_END) {
1015 | String ucLine = line.toUpperCase();
1016 | if (ucLine.startsWith(DELIMITER_STATEMENT)) {
1017 | fileDelimeter = line.substring(DELIMITER_STATEMENT_LENGTH).trim();
1018 | continue;
1019 | }
1020 | }
1021 |
1022 | if (!keepFormat) {
1023 | sql.append(" ").append(line);
1024 | } else {
1025 | sql.append("\n").append(line);
1026 | }
1027 |
1028 | overflow = SqlSplitter.containsSqlEnd(line, fileDelimeter, overflow);
1029 |
1030 | // SQL defines "--" as a comment to EOL
1031 | // and in Oracle it may contain a hint
1032 | // so we cannot just remove it, instead we must end it
1033 | if (!keepFormat && overflow == SqlSplitter.NO_END) {
1034 | sql.append("\n");
1035 | }
1036 |
1037 | if ((delimiterType.equals(DelimiterType.NORMAL) && overflow > 0)
1038 | || (delimiterType.equals(DelimiterType.ROW) && line.trim().equals(fileDelimeter))) {
1039 | execSQL(sql.substring(0, sql.length() - fileDelimeter.length()), out);
1040 | sql.setLength(0); // clean buffer
1041 | overflow = SqlSplitter.NO_END;
1042 | }
1043 | }
1044 |
1045 | // Catch any statements not followed by ;
1046 | if (sql.length() > 0) {
1047 | execSQL(sql.toString(), out);
1048 | }
1049 | }
1050 |
1051 | /**
1052 | * Exec the sql statement.
1053 | *
1054 | * @param sql query to execute
1055 | * @param out the outputstream
1056 | */
1057 | private void execSQL(String sql, PrintStream out) throws SQLException {
1058 | // Check and ignore empty statements
1059 | if (sql.trim().isEmpty()) {
1060 | return;
1061 | }
1062 |
1063 | ResultSet resultSet = null;
1064 | try {
1065 | totalStatements++;
1066 | getLog().debug("SQL: " + sql);
1067 |
1068 | boolean ret;
1069 | int updateCountTotal = 0;
1070 |
1071 | ret = statement.execute(sql);
1072 | do {
1073 | if (!ret) {
1074 | int updateCount = statement.getUpdateCount();
1075 | if (updateCount != -1) {
1076 | updateCountTotal += updateCount;
1077 | }
1078 | } else {
1079 | resultSet = statement.getResultSet();
1080 | if (printResultSet) {
1081 | printResultSet(resultSet, out);
1082 | }
1083 | }
1084 | ret = statement.getMoreResults();
1085 | } while (ret);
1086 |
1087 | getLog().debug(updateCountTotal + " rows affected");
1088 |
1089 | if (printResultSet && showFooter) {
1090 | printResultSetCount(updateCountTotal, out);
1091 | }
1092 |
1093 | SQLWarning warning = conn.getWarnings();
1094 | while (warning != null) {
1095 | getLog().debug(warning + " sql warning");
1096 | warning = warning.getNextWarning();
1097 | }
1098 | conn.clearWarnings();
1099 | successfulStatements++;
1100 | } catch (SQLException e) {
1101 | getLog().error("Failed to execute: " + sql);
1102 | if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) {
1103 | throw e;
1104 | }
1105 | getLog().error(e.toString());
1106 | } finally {
1107 | if (resultSet != null) {
1108 | resultSet.close();
1109 | }
1110 | }
1111 | }
1112 |
1113 | /**
1114 | * print any results in the result set.
1115 | *
1116 | * @param rs the resultset to print information about
1117 | * @param out the place to print results
1118 | * @throws SQLException on SQL problems.
1119 | */
1120 | protected void printResultSet(ResultSet rs, PrintStream out) throws SQLException {
1121 | if (rs != null) {
1122 | getLog().debug("Processing new result set.");
1123 | ResultSetMetaData md = rs.getMetaData();
1124 | int columnCount = md.getColumnCount();
1125 | StringBuffer line = new StringBuffer();
1126 | if (showHeaders) {
1127 | boolean first = true;
1128 | for (int col = 1; col <= columnCount; col++) {
1129 | String columnValue = md.getColumnName(col);
1130 |
1131 | if (columnValue != null) {
1132 | columnValue = columnValue.trim();
1133 |
1134 | if (",".equals(outputDelimiter)) {
1135 | columnValue = StringEscapeUtils.escapeCsv(columnValue);
1136 | }
1137 | }
1138 |
1139 | if (first) {
1140 | first = false;
1141 | } else {
1142 | line.append(outputDelimiter);
1143 | }
1144 | line.append(columnValue);
1145 | }
1146 | out.println(line);
1147 | line = new StringBuffer();
1148 | }
1149 | while (rs.next()) {
1150 | boolean first = true;
1151 | for (int col = 1; col <= columnCount; col++) {
1152 | String columnValue = rs.getString(col);
1153 | if (columnValue != null) {
1154 | columnValue = columnValue.trim();
1155 |
1156 | if (",".equals(outputDelimiter)) {
1157 | columnValue = StringEscapeUtils.escapeCsv(columnValue);
1158 | }
1159 | }
1160 |
1161 | if (first) {
1162 | first = false;
1163 | } else {
1164 | line.append(outputDelimiter);
1165 | }
1166 | line.append(columnValue);
1167 | }
1168 | out.println(line);
1169 | line = new StringBuffer();
1170 | }
1171 | }
1172 | out.println();
1173 | }
1174 |
1175 | protected void printResultSetCount(int updateCountTotal, PrintStream out) {
1176 | out.println(updateCountTotal + " rows affected");
1177 | }
1178 |
1179 | /**
1180 | * Contains the definition of a new transaction element. Transactions allow several files or blocks of statements to
1181 | * be executed using the same JDBC connection and commit operation in between.
1182 | */
1183 | private class Transaction implements Comparable {
1184 | private File tSrcFile = null;
1185 |
1186 | private String tSqlCommand = "";
1187 |
1188 | /**
1189 | *
1190 | */
1191 | public void setSrc(File src) {
1192 | this.tSrcFile = src;
1193 | }
1194 |
1195 | /**
1196 | *
1197 | */
1198 | public void addText(String sql) {
1199 | this.tSqlCommand += sql;
1200 | }
1201 |
1202 | /**
1203 | *
1204 | */
1205 | private void runTransaction(PrintStream out) throws IOException, SQLException {
1206 | if (!tSqlCommand.isEmpty()) {
1207 | getLog().info("Executing commands");
1208 |
1209 | runStatements(new StringReader(tSqlCommand), out);
1210 | }
1211 |
1212 | if (tSrcFile != null) {
1213 | getLog().info("Executing file: " + tSrcFile.getAbsolutePath());
1214 |
1215 | Reader reader = null;
1216 |
1217 | if (StringUtils.isEmpty(encoding)) {
1218 | reader = new FileReader(tSrcFile);
1219 | } else {
1220 | reader = new InputStreamReader(Files.newInputStream(tSrcFile.toPath()), encoding);
1221 | }
1222 |
1223 | try {
1224 | runStatements(reader, out);
1225 | } finally {
1226 | reader.close();
1227 | }
1228 | }
1229 | }
1230 |
1231 | public int compareTo(Transaction transaction) {
1232 | if (transaction.tSrcFile == null) {
1233 | if (this.tSrcFile == null) {
1234 | return 0;
1235 | } else {
1236 | return Integer.MAX_VALUE;
1237 | }
1238 | } else {
1239 | if (this.tSrcFile == null) {
1240 | return Integer.MIN_VALUE;
1241 | } else {
1242 | return this.tSrcFile.compareTo(transaction.tSrcFile);
1243 | }
1244 | }
1245 | }
1246 | }
1247 |
1248 | //
1249 | // helper accessors for unit test purposes
1250 | //
1251 |
1252 | public String getUsername() {
1253 | return this.username;
1254 | }
1255 |
1256 | public void setUsername(String username) {
1257 | this.username = username;
1258 | }
1259 |
1260 | public String getPassword() {
1261 | return this.password;
1262 | }
1263 |
1264 | public void setPassword(String password) {
1265 | this.password = password;
1266 | }
1267 |
1268 | public String getUrl() {
1269 | return this.url;
1270 | }
1271 |
1272 | public void setUrl(String url) {
1273 | this.url = url;
1274 | }
1275 |
1276 | public String getDriver() {
1277 | return this.driver;
1278 | }
1279 |
1280 | public void setDriver(String driver) {
1281 | this.driver = driver;
1282 | }
1283 |
1284 | void setAutocommit(boolean autocommit) {
1285 | this.autocommit = autocommit;
1286 | }
1287 |
1288 | public void setRollbackTransactions(boolean rollbackTransactions) {
1289 | this.rollbackTransactions = rollbackTransactions;
1290 | }
1291 |
1292 | void setFileset(Fileset fileset) {
1293 | this.fileset = fileset;
1294 | }
1295 |
1296 | public File[] getSrcFiles() {
1297 | return this.srcFiles;
1298 | }
1299 |
1300 | public void setSrcFiles(File[] files) {
1301 | this.srcFiles = files;
1302 | }
1303 |
1304 | public String getOrderFile() {
1305 | return this.orderFile;
1306 | }
1307 |
1308 | public void setOrderFile(String orderFile) {
1309 | if (FILE_SORTING_ASC.equalsIgnoreCase(orderFile)) {
1310 | this.orderFile = FILE_SORTING_ASC;
1311 | } else if (FILE_SORTING_DSC.equalsIgnoreCase(orderFile)) {
1312 | this.orderFile = FILE_SORTING_DSC;
1313 | } else {
1314 | throw new IllegalArgumentException(orderFile + " is not a valid value for orderFile, only '"
1315 | + FILE_SORTING_ASC + "' or '" + FILE_SORTING_DSC + "'.");
1316 | }
1317 | }
1318 |
1319 | /**
1320 | * Number of SQL statements executed so far that caused errors.
1321 | *
1322 | * @return the number
1323 | */
1324 | public int getSuccessfulStatements() {
1325 | return successfulStatements;
1326 | }
1327 |
1328 | /**
1329 | * Number of SQL statements executed so far, including the ones that caused errors.
1330 | *
1331 | * @return the number
1332 | */
1333 | public int getTotalStatements() {
1334 | return totalStatements;
1335 | }
1336 |
1337 | public String getOnError() {
1338 | return this.onError;
1339 | }
1340 |
1341 | public void setOnError(String action) {
1342 | if (ON_ERROR_ABORT.equalsIgnoreCase(action)) {
1343 | this.onError = ON_ERROR_ABORT;
1344 | } else if (ON_ERROR_CONTINUE.equalsIgnoreCase(action)) {
1345 | this.onError = ON_ERROR_CONTINUE;
1346 | } else if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(action)) {
1347 | this.onError = ON_ERROR_ABORT_AFTER;
1348 | } else {
1349 | throw new IllegalArgumentException(action + " is not a valid value for onError, only '" + ON_ERROR_ABORT
1350 | + "', '" + ON_ERROR_ABORT_AFTER + "', or '" + ON_ERROR_CONTINUE + "'.");
1351 | }
1352 | }
1353 |
1354 | /**
1355 | * Validate the database connection by executing all validation SQLs.
1356 | *
1357 | * @param conn the open database connection
1358 | * @throws java.sql.SQLException if SQL execution fails
1359 | */
1360 | protected void validateConnection(Connection conn) throws SQLException {
1361 | if (connectionValidationSqls != null && !connectionValidationSqls.isEmpty()) {
1362 | for (String sql : connectionValidationSqls) {
1363 | if (sql != null && sql.trim().length() > 0) {
1364 | conn.createStatement().executeQuery(sql);
1365 | }
1366 | }
1367 | }
1368 | }
1369 |
1370 | /**
1371 | * Waits the time specified in connectionRetryInterval.
1372 | *
1373 | * @throws MojoExecutionException
1374 | */
1375 | private void waitToRetryConnectionValidation() throws MojoExecutionException {
1376 | try {
1377 | Thread.sleep(this.connectionRetryInterval * 1000);
1378 | } catch (InterruptedException e) {
1379 | throw new MojoExecutionException(e.getMessage(), e);
1380 | }
1381 | }
1382 |
1383 | /**
1384 | * Closes the database connection.
1385 | */
1386 | void closeConnection() {
1387 | if (conn != null) {
1388 | try {
1389 | conn.close();
1390 | } catch (SQLException e) {
1391 | getLog().debug("Failed to close connection: " + e.getMessage());
1392 | }
1393 | }
1394 | }
1395 |
1396 | /**
1397 | * Closes the database statement.
1398 | */
1399 | private void closeStatement() {
1400 | if (statement != null) {
1401 | try {
1402 | statement.close();
1403 | } catch (SQLException e) {
1404 | getLog().debug("Failed to close statement: " + e.getMessage());
1405 | }
1406 | }
1407 | }
1408 |
1409 | /**
1410 | * Check is the database connection closed.
1411 | *
1412 | * @return true if the connection is closed, otherwise false
1413 | */
1414 | protected boolean isConnectionClosed() {
1415 | try {
1416 | return conn == null || conn.isClosed();
1417 | } catch (SQLException e) {
1418 | return false;
1419 | }
1420 | }
1421 |
1422 | protected void clear() {
1423 | sqlCommand = null;
1424 | if (transactions != null) {
1425 | transactions.clear();
1426 | }
1427 | }
1428 |
1429 | protected int getConnectionRetryAttempts() {
1430 | return this.connectionRetryAttempts;
1431 | }
1432 |
1433 | public void setConnectionValidationSqls(List connectionValidationSql) {
1434 | this.connectionValidationSqls = connectionValidationSql;
1435 | }
1436 |
1437 | public void setConnectionRetryCount(int connectionRetryCount) {
1438 | this.connectionRetryCount = connectionRetryCount;
1439 | }
1440 |
1441 | public void setConnectionRetryInterval(int connectionRetryInterval) {
1442 | this.connectionRetryInterval = connectionRetryInterval;
1443 | }
1444 |
1445 | void setSettings(Settings settings) {
1446 | this.settings = settings;
1447 | }
1448 |
1449 | void setSettingsKey(String key) {
1450 | this.settingsKey = key;
1451 | }
1452 |
1453 | void setSkip(boolean skip) {
1454 | this.skip = skip;
1455 | }
1456 |
1457 | public void setSkipMissingFiles(boolean skipMissingFiles) {
1458 | this.skipMissingFiles = skipMissingFiles;
1459 | }
1460 |
1461 | public void setDriverProperties(String driverProperties) {
1462 | this.driverProperties = driverProperties;
1463 | }
1464 |
1465 | public String getSqlCommand() {
1466 | return sqlCommand;
1467 | }
1468 |
1469 | public void setSqlCommand(String sqlCommand) {
1470 | this.sqlCommand = sqlCommand;
1471 | }
1472 |
1473 | public List getTransactions() {
1474 | return transactions;
1475 | }
1476 |
1477 | public void setTransactions(List transactions) {
1478 | this.transactions = transactions;
1479 | }
1480 |
1481 | public void setFileFilter(MavenFileFilter filter) {
1482 | this.fileFilter = filter;
1483 | }
1484 |
1485 | public void setSecurityDispatcher(SecDispatcher securityDispatcher) {
1486 | this.securityDispatcher = securityDispatcher;
1487 | }
1488 |
1489 | public void setOutputDelimiter(String outputDelimiter) {
1490 | this.outputDelimiter = outputDelimiter;
1491 | }
1492 |
1493 | public void setOutputEncoding(String outputEncoding) {
1494 | this.outputEncoding = outputEncoding;
1495 | }
1496 |
1497 | public void setShowFooter(boolean showFooter) {
1498 | this.showFooter = showFooter;
1499 | }
1500 | }
1501 |
--------------------------------------------------------------------------------