diff --git a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java
index 50ada18..b90a262 100644
--- a/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java
+++ b/src/main/java/fr/greencodeinitiative/java/JavaCheckRegistrar.java
@@ -35,6 +35,7 @@
import fr.greencodeinitiative.java.checks.InitializeBufferWithAppropriateSize;
import fr.greencodeinitiative.java.checks.NoFunctionCallWhenDeclaringForLoop;
import fr.greencodeinitiative.java.checks.OptimizeReadFileExceptions;
+import fr.greencodeinitiative.java.checks.UseEveryColumnQueried;
import org.sonar.plugins.java.api.CheckRegistrar;
import org.sonar.plugins.java.api.JavaCheck;
import org.sonarsource.api.sonarlint.SonarLintSide;
@@ -62,7 +63,8 @@ public class JavaCheckRegistrar implements CheckRegistrar {
InitializeBufferWithAppropriateSize.class,
AvoidSetConstantInBatchUpdate.class,
FreeResourcesOfAutoCloseableInterface.class,
- AvoidMultipleIfElseStatement.class
+ AvoidMultipleIfElseStatement.class,
+ UseEveryColumnQueried.class
);
/**
diff --git a/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java
new file mode 100644
index 0000000..7e85e11
--- /dev/null
+++ b/src/main/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueried.java
@@ -0,0 +1,357 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.sonar.check.Rule;
+import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
+import org.sonar.plugins.java.api.semantic.MethodMatchers;
+import org.sonar.plugins.java.api.semantic.Symbol;
+import org.sonar.plugins.java.api.semantic.Symbol.VariableSymbol;
+import org.sonar.plugins.java.api.tree.Arguments;
+import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
+import org.sonar.plugins.java.api.tree.ExpressionTree;
+import org.sonar.plugins.java.api.tree.IdentifierTree;
+import org.sonar.plugins.java.api.tree.LiteralTree;
+import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
+import org.sonar.plugins.java.api.tree.MethodInvocationTree;
+import org.sonar.plugins.java.api.tree.Tree;
+import org.sonar.plugins.java.api.tree.VariableTree;
+import org.sonar.plugins.java.api.tree.Tree.Kind;
+
+@Rule(key = "EC1044")
+public class UseEveryColumnQueried extends IssuableSubscriptionVisitor {
+
+ protected static final String MESSAGERULE = "Avoid querying SQL columns that are not used";
+ private static final String JAVA_SQL_STATEMENT = "java.sql.Statement";
+ private static final String JAVA_SQL_RESULTSET = "java.sql.ResultSet";
+ private static final MethodMatchers SQL_STATEMENT_DECLARE_SQL = MethodMatchers.create()
+ .ofSubTypes(JAVA_SQL_STATEMENT)
+ .names("executeQuery", "execute")
+ .addParametersMatcher("java.lang.String")
+ .build();
+ private static final MethodMatchers SQL_STATEMENT_RETRIEVE_RESULTSET = MethodMatchers.create()
+ .ofSubTypes(JAVA_SQL_STATEMENT)
+ .names("executeQuery", "getResultSet")
+ .withAnyParameters()
+ .build();
+ private static final MethodMatchers SQL_RESULTSET_GET_COLNAME = MethodMatchers.create()
+ .ofSubTypes(JAVA_SQL_RESULTSET)
+ .name(n -> n.startsWith("get"))
+ .addParametersMatcher("java.lang.String")
+ .build();
+ private static final MethodMatchers SQL_RESULTSET_GET_COLID = MethodMatchers.create()
+ .ofSubTypes(JAVA_SQL_RESULTSET)
+ .name(n -> n.startsWith("get"))
+ .addParametersMatcher("int")
+ .build();
+ private static final Pattern SELECTED_COLUMNS_PATTERN = Pattern.compile("SELECT\\s+(.*)\\s+FROM\\s+.*");
+
+ @Override
+ public List nodesToVisit() {
+ return Arrays.asList(Kind.METHOD_INVOCATION);
+ }
+
+ /**
+ * How this rule works :
+ * We start from the method invocation that declares the SQL query ( stmt.executeQuery("SELECT ... FROM ...") )
+ * the selected columns are directly extracted from the parameters of this method invocation
+ * We explore the stmt object to find where the method invocation that returns the ResultSet object
+ * finally we explore all invocations of this ResultSet object to list all the column used.
+ * the selected and used columns are compared, and an issue is reported if columns are selected but not used.
+ *
+ *
+ * stmt.execute("SELECT ... FROM ...") or stmt.executeQuery(...)
+ * | |
+ * | ----> Selected Columns
+ * [Statement Object]
+ * |
+ * |
+ * v
+ * res = stmt.getResultSet() or stmt.executeQuery(...)
+ * |
+ * |
+ * [ResultSet Object]
+ * |
+ * |
+ * v
+ * res.getInt(...) or any column extraction method
+ * |
+ * ----> Used Column
+ *
+ */
+ @Override
+ public void visitNode(Tree tree) {
+ MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
+ if (!SQL_STATEMENT_DECLARE_SQL.matches(methodInvocationTree)) {
+ return;
+ }
+
+ // extraction of the selected columns
+ List selectedColumns = getSelectedColumns(methodInvocationTree);
+ if (selectedColumns.isEmpty()) {
+ return;
+ }
+
+ //if selected columns includes "*", stop the search
+ if (selectedColumns.contains("*")) {
+ return;
+ }
+
+ // get the ResultSet object and check it's validity
+ Symbol resultSet = getResultSetNode(methodInvocationTree);
+ if (resultSet == null) {
+ return;
+ }
+ if(isResultSetInvalid(resultSet)){
+ return;
+ }
+
+ // extraction of the used columns
+ List usedColumns = getUsedColumns(resultSet, selectedColumns);
+
+ // if there are selected columns that are not used in the code, report the issue
+ List differences = selectedColumns.stream()
+ .filter(element -> !usedColumns.contains(element))
+ .collect(Collectors.toList());
+ if (!differences.isEmpty()) {
+ reportIssue(methodInvocationTree, MESSAGERULE);
+ }
+ }
+
+ private static List getSelectedColumns(MethodInvocationTree methodInvocationTree) {
+ // get the first argument of the query definition method
+ Arguments arguments = methodInvocationTree.arguments();
+ if (arguments.isEmpty()) {
+ return new ArrayList<>();
+ }
+ ExpressionTree argument = arguments.get(0);
+ // get the contents of the string in this first parameters
+ LiteralTree literal = extractLiteralFromVariable(argument);
+ if (literal == null) {
+ return new ArrayList<>();
+ }
+ String query = literal.value();
+ //get the list of selected columns from this string
+ return extractSelectedSQLColumns(query);
+ }
+
+ /**
+ * returns a list of used columns from a resultset object and a list of the selected columns
+ */
+ private static List getUsedColumns(Symbol resultSet, List selectedColumns) {
+ // iterate across all usages of the ResultSet
+ List usedColumns = new ArrayList<>();
+ List resultSetUsages = resultSet.usages();
+ for (IdentifierTree usage : resultSetUsages) {
+ // check this usage is an assignement, and the method parameters
+ if (usage.parent().is(Tree.Kind.ASSIGNMENT)) {
+ break;
+ }
+ MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage);
+ if (methodInvocation == null || methodInvocation.arguments().isEmpty()) {
+ continue;
+ }
+ // get the value of the first parameter
+ ExpressionTree parameter = methodInvocation.arguments().get(0);
+ LiteralTree columnGot = extractLiteralFromVariable(parameter);
+ if (columnGot == null) {
+ continue;
+ }
+ String column;
+ String value = columnGot.value();
+ // if this first parameter is a string, clean up and use as is for used column name
+ if (SQL_RESULTSET_GET_COLNAME.matches(methodInvocation) && columnGot.is(Tree.Kind.STRING_LITERAL)) {
+ column = value.toUpperCase()
+ .replaceAll("^['\"]", "")
+ .replaceAll("['\"]$", "");
+ // if this first parameter is an int, use as and id for used column name
+ } else if (SQL_RESULTSET_GET_COLID.matches(methodInvocation) && columnGot.is(Tree.Kind.INT_LITERAL)) {
+ int columnId = Integer.parseInt(value);
+ if (columnId > selectedColumns.size()) {
+ break;
+ }
+ column = selectedColumns.get(columnId - 1);
+ } else {
+ continue;
+ }
+ usedColumns.add(column);
+ }
+ return usedColumns;
+ }
+
+ /**
+ * returns the litteral assigned to a variable var
+ * var has to be either a litteral itself or a final variable
+ * returns null if the ExpressionTree is not a variable,
+ * if the variable is not final, or if the variable has not been initialized
+ */
+ @Nullable
+ private static LiteralTree extractLiteralFromVariable(ExpressionTree tree) {
+ if (tree instanceof LiteralTree) {
+ return (LiteralTree) tree;
+ }
+ if (!tree.is(Tree.Kind.IDENTIFIER)) {
+ return null;
+ }
+ IdentifierTree identifierTree = (IdentifierTree) tree;
+ Symbol symbol = identifierTree.symbol();
+ if (symbol == null || !symbol.isFinal() || !symbol.isVariableSymbol()) {
+ return null;
+ }
+ VariableSymbol variableSymbol = (VariableSymbol) symbol;
+ Tree assignment = variableSymbol.declaration();
+ if (!assignment.is(Tree.Kind.VARIABLE)) {
+ return null;
+ }
+ VariableTree variableTree = (VariableTree) assignment;
+ ExpressionTree initializer = variableTree.initializer();
+ if (initializer instanceof LiteralTree) {
+ return (LiteralTree) initializer;
+ }
+ return null;
+ }
+
+ /**
+ * get the ResultSet Object assigned from the result of the retrieve resultset method
+ * from the sql declaration method (via the shared stmt object)
+ * stmt.execute(...) -> rs = stmt.getResultSet()
+ */
+ @Nullable
+ private static Symbol getResultSetNode(MethodInvocationTree methodInvocationTree) {
+ // get the Statement object on witch the method is called
+ ExpressionTree et = methodInvocationTree.methodSelect();
+ if (!et.is(Tree.Kind.MEMBER_SELECT)) {
+ return null;
+ }
+ MemberSelectExpressionTree mset = (MemberSelectExpressionTree) et;
+ ExpressionTree expression = mset.expression();
+ if (!expression.is(Tree.Kind.IDENTIFIER)) {
+ return null;
+ }
+ IdentifierTree id = (IdentifierTree) expression;
+ Symbol statement = id.symbol();
+ if (statement == null) {
+ return null;
+ }
+ // iterate over all usages of this Statement object
+ List usages = statement.usages();
+ Symbol resultSet = null;
+ for (IdentifierTree usage : usages) {
+ // does this usage of the Statement object match SQL_STATEMENT_RETRIEVE_RESULTSET ?
+ MethodInvocationTree methodInvocation = getMethodInvocationFromTree(usage);
+ if (methodInvocation == null || !SQL_STATEMENT_RETRIEVE_RESULTSET.matches(methodInvocation)) {
+ continue;
+ }
+ // if so end the search, we have found our resultSet object
+ Tree parent = methodInvocation.parent();
+ if (parent.is(Tree.Kind.VARIABLE)) {
+ resultSet = ((VariableTree) parent).symbol();
+ break;
+ }
+ }
+ return resultSet;
+ }
+
+ /**
+ * unpacks a chain call to get the method invocation node
+ * example : this.object.chain.method() -> method()
+ */
+ @Nullable
+ private static MethodInvocationTree getMethodInvocationFromTree(IdentifierTree tree) {
+ Tree parent = tree;
+ while (parent != null && !parent.is(Tree.Kind.METHOD_INVOCATION)) {
+ parent = parent.parent();
+ }
+ return (MethodInvocationTree) parent;
+ }
+
+ /**
+ * checks the two conditions that make a ResultSet object invalid,
+ * and would stop the search for used columns because of side effects
+ * - the ResultSet object being passed in a method
+ * - the ResultSet object being reassigned
+ */
+ private static boolean isResultSetInvalid(Symbol resultSet) {
+ return isObjectUsedInMethodParameters(resultSet)
+ || isObjectReassigned(resultSet);
+ }
+
+ /**
+ * checks if an object is used as a parameter a method
+ */
+ private static boolean isObjectUsedInMethodParameters(Symbol obj) {
+ List usages = obj.usages();
+ for (IdentifierTree usage : usages) {
+ Tree parent = usage.parent();
+ if (parent.is(Tree.Kind.ARGUMENTS)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * checks if an object is reassigned
+ */
+ private static boolean isObjectReassigned(Symbol obj) {
+ List usages = obj.usages();
+ for (IdentifierTree usage : usages) {
+ Tree parent = usage.parent();
+ if (parent.is(Tree.Kind.ASSIGNMENT)) {
+ AssignmentExpressionTree assignment = (AssignmentExpressionTree) parent;
+ ExpressionTree expressionTree = assignment.variable();
+ if (expressionTree.is(Tree.Kind.IDENTIFIER)
+ && obj.equals(((IdentifierTree) expressionTree).symbol())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * extract from a SQL query in the form of "SELECT X, Y AS Z FROM TABLE ..."
+ * a list of all the column names and aliases (X and Z) without whitespace and in uppercase
+ */
+ static List extractSelectedSQLColumns(String query) {
+ if (query == null) {
+ return new ArrayList<>();
+ }
+ query = query.toUpperCase()
+ .replaceAll("^['\"]", "")
+ .replaceAll("['\"]$", "");
+ List columns = new ArrayList<>();
+ Matcher matcher = SELECTED_COLUMNS_PATTERN.matcher(query);
+ if (matcher.matches()) {
+ String columnString = matcher.group(1);
+ columns = Arrays.asList(columnString.split(","));
+ columns.replaceAll(column -> column.replaceAll("\\s+", " "));
+ columns.replaceAll(column -> column.contains(" AS ") ? column.split(" AS ")[1].trim() : column.trim());
+ }
+ return columns;
+ }
+}
diff --git a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json
index 88e381a..b2f127c 100644
--- a/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json
+++ b/src/main/resources/fr/greencodeinitiative/java/ecoCode_way_profile.json
@@ -16,6 +16,7 @@
"EC76",
"EC77",
"EC78",
- "EC79"
+ "EC79",
+ "EC1044"
]
}
diff --git a/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java
new file mode 100644
index 0000000..c12e375
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java
@@ -0,0 +1,54 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * This is the nominal test case, where the SQL query is an attribute of the class.
+ * All Fields are accessed, so no issue is raised
+ */
+public class AttributeQueryCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(QUERY);) {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", Age: " + rs.getInt("age"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java
new file mode 100644
index 0000000..e42ff48
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java
@@ -0,0 +1,53 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * This is the nominal test case, where the SQL query is an attribute of the class.
+ * One field is not accesed, so an issue is raised
+ */
+public class AttributeQueryNonCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}}
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java
new file mode 100644
index 0000000..6146dfa
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java
@@ -0,0 +1,55 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, the query is a litteral string directly inserted in the method
+ * All Fields are accessed, so no issue is raised
+ */
+public class LitteralQueryCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", Age: " + rs.getInt("age"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+}
diff --git a/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java
new file mode 100644
index 0000000..476233d
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java
@@ -0,0 +1,54 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, the query is a litteral string directly inserted in the method
+ * One field is not accesed, so an issue is raised
+ */
+public class LitteralQueryNonCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}}
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java
new file mode 100644
index 0000000..94f7a29
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java
@@ -0,0 +1,69 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, multiple queries are done using the same Statement Object.
+ * All Fields are accessed, so no issue is raised
+ */
+public class MultipleQueriesCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+ private static final String QUERY2 = "SELECT id, first, last FROM Registration2";
+
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ) {
+
+ ResultSet rs = stmt.executeQuery(QUERY);
+ while (rs.next()) {
+ // Display values
+ System.out.print("Age: " + rs.getInt("age"));
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ rs = stmt.executeQuery(QUERY2);
+
+
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java
new file mode 100644
index 0000000..f7cf12c
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java
@@ -0,0 +1,68 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, multiple queries are done using the same Statement Object.
+ * One field is not accesed, so an issue is raised
+ */
+public class MultipleQueriesNonCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+ private static final String QUERY2 = "SELECT id, first, last, age FROM Registration2";
+
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ) {
+
+ ResultSet rs = stmt.executeQuery(QUERY); // Noncompliant {{Avoid querying SQL columns that are not used}}
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ rs = stmt.executeQuery(QUERY2);
+
+ while (rs.next()) {
+ // Display values
+ System.out.print("Age: " + rs.getInt("age"));
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+
+
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/SelectStar.java b/src/test/files/UseEveryColumnQueried/SelectStar.java
new file mode 100644
index 0000000..84fe348
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/SelectStar.java
@@ -0,0 +1,55 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, we use a "select * from" statement
+ * Since we can't know what columns exist, we can't know if all columns are being used, no issue is raised
+ * "select * from" is bad practice but is already covered by EC74
+ */
+public class SelectStar {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT * FROM Registration";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(QUERY);) {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", Age: " + rs.getInt("age"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java
new file mode 100644
index 0000000..03ac224
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java
@@ -0,0 +1,56 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, columns are accesed by IDs and names, some of them being in final variables
+ * All Fields are accessed, so no issue is raised
+ */
+public class UseColumnIdsAndNameAttributesCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+ private static final String ID = "id";
+ private static final int AGE = 4;
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(QUERY);) {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt(ID));
+ System.out.print(", Age: " + rs.getInt(AGE));
+ System.out.print(", First: " + rs.getString(2));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java
new file mode 100644
index 0000000..c7972d4
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java
@@ -0,0 +1,54 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, columns are accesed by IDs and names, some of them being in final variables
+ * One field is not accesed, so an issue is raised
+ */
+public class UseColumnIdsAndNameAttributesNonCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+ private static final String QUERY = "SELECT id, first, last, age FROM Registration";
+ private static final String ID = "id";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(QUERY);) { // Noncompliant {{Avoid querying SQL columns that are not used}}
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt(ID));
+ System.out.print(", First: " + rs.getString(2));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java
new file mode 100644
index 0000000..db7ae3e
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/UseMethodCompliant.java
@@ -0,0 +1,57 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, the ResultSet is passed through a method
+ * All Fields are accessed, so no issue is raised
+ */
+public class UseMethodCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) {
+ extractGet(rs);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private void extractGet(ResultSet rs) throws SQLException {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", Age: " + rs.getInt("age"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ }
+}
diff --git a/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java
new file mode 100644
index 0000000..559c06b
--- /dev/null
+++ b/src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java
@@ -0,0 +1,56 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * In this test case, the ResultSet is passed through a method
+ * One field is not accesed, so an issue is raised
+ */
+public class UseMethodNonCompliant {
+
+ private static final String DB_URL = "jdbc:mysql://localhost/TEST";
+ private static final String USER = "guest";
+ private static final String PASS = "guest123";
+
+ public void callJdbc() {
+
+ try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery("SELECT id, first, last, age FROM Registration");) { // Noncompliant {{Avoid querying SQL columns that are not used}}
+ extractGet(rs);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private void extractGet(ResultSet rs) throws SQLException {
+ while (rs.next()) {
+ // Display values
+ System.out.print("ID: " + rs.getInt("id"));
+ System.out.print(", First: " + rs.getString("first"));
+ System.out.println(", Last: " + rs.getString("last"));
+ }
+ }
+}
diff --git a/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java
new file mode 100644
index 0000000..edf2b76
--- /dev/null
+++ b/src/test/java/fr/greencodeinitiative/java/checks/UseEveryColumnQueriedTest.java
@@ -0,0 +1,131 @@
+/*
+ * ecoCode - Java language - Provides rules to reduce the environmental footprint of your Java programs
+ * Copyright © 2023 Green Code Initiative (https://www.ecocode.io)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package fr.greencodeinitiative.java.checks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.sonar.java.checks.verifier.CheckVerifier;
+
+class UseEveryColumnQueriedTest {
+
+ @Test
+ void testExtractSelectedSQLColumns(){
+ String query = "\"SELECT id AS registration_id,\tfirst, last as Final, AGE FROM Registration\"";
+ List columns = UseEveryColumnQueried.extractSelectedSQLColumns(query);
+ assertEquals(4, columns.size());
+ assertEquals("REGISTRATION_ID", columns.get(0));
+ assertEquals("FIRST", columns.get(1));
+ assertEquals("FINAL", columns.get(2));
+ assertEquals("AGE", columns.get(3));
+ }
+
+ @Test
+ void testHasIssues1() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryNonCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyIssues();
+ }
+
+ @Test
+ void testHasIssues2() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryNonCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyIssues();
+ }
+
+ @Test
+ void testHasIssues3() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesNonCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyIssues();
+ }
+
+ @Test
+ @Disabled // case not handled (multiple queries with the same ResultSet object)
+ void testHasIssues4() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesNonCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyIssues();
+ }
+
+ @Test
+ @Disabled // case not handled (usage of a method)
+ void testHasIssues5() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/UseMethodNonCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyIssues();
+ }
+
+
+ @Test
+ void testHasNoIssues1() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/AttributeQueryCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+
+ @Test
+ void testHasNoIssues2() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/LitteralQueryCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+
+ @Test
+ void testHasNoIssues3() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/UseColumnIdsAndNameAttributesCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+
+ @Test
+ void testHasNoIssues4() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/MultipleQueriesCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+
+ @Test
+ void testHasNoIssues5() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/UseMethodCompliant.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+
+ @Test
+ void testHasNoIssues6() {
+ CheckVerifier.newVerifier()
+ .onFile("src/test/files/UseEveryColumnQueried/SelectStar.java")
+ .withCheck(new UseEveryColumnQueried())
+ .verifyNoIssues();
+ }
+}