-1

I am creating SQL SELECT query String, by taking two lists and iterating over them in parallel.

List<String> selectFields = new ArrayList<>();
List<String> uuidFunction = new ArrayList<>();
selectFields.add("id");
selectFields.add("name");

uuidFunction.add("UUID_TO_STR");
uuidFunction.add(null);

StringBuilder sql = new StringBuilder("SELECT ");

IntStream.range(0, selectFields.size())
        .parallel()
        .forEach(i -> {
          sql.append(
              uuidFunction.get(i) != null ? uuidFunction.get(i) + "(" + selectFields.get(i) + ")"
                  : selectFields.get(i));
        }
        );
sql.append(" FROM test_table"); 

This gives me a nice SQL String BUT without commans , between each element.

SELECT nameUUID_TO_STR(id)

Why can't I use .collect(Collectors.joining(",")) right after forEach ?

NOTE this problem is part of a bigger function in my Project where are are not using direct SQL Prepared Statements. So please don't provide SQL way of solving this problem. This is a generic problem related to iterations and appending Strings.

Lino
  • 19,604
  • 6
  • 47
  • 65
Anum Sheraz
  • 2,383
  • 1
  • 29
  • 54
  • 3
    I don't know streams very well, but I do know SQL injection, which is what you'll get by not using a statement to generate your queries. Use statements please. – Tim Biegeleisen Jun 01 '18 at 13:25
  • `forEach` is a terminal operation with no return value. That is why you can't collect it – Lino Jun 01 '18 at 13:47
  • The only work around solution I have is to append the string with "," inside forEach and then removing the last "," right after exiting forEach `sql.deleteCharAt(sql.length()-1)` . Any better solution ? – Anum Sheraz Jun 01 '18 at 13:49
  • @TimBiegeleisen actually [Java's PreparedStatements don't handle column name selection](https://stackoverflow.com/questions/3135973/variable-column-names-using-prepared-statements), and given OP's comment that the query construction is part of a bigger codebase, for all we know the string might be later fed to a PreparedStatement. I admit I had the same reaction though. – Aaron Jun 01 '18 at 14:11
  • @Aaron If the OP is building queries with no input from the outside, then there is nothing wrong here. If there is input from the outside, then use separate statements for queries with different columns/tables. – Tim Biegeleisen Jun 01 '18 at 14:15
  • @TimBiegeleisen wouldn't feeding the concatenated statement to a PreparedStatement be just as safe and more easily maintainable? I.e. `conn.prepareStatement("SELECT "+ userRequestedFields.join(",") + " FROM TABLE WHERE admin=0")`. I imagine it would raise an error if the resulting query ended up as `SELECT * FROM TABLE;--, FROM TABLE WHERE admin=0` although I can't find documentation on this. Anyway I agree with the linked question's answer that users shouldn't (have to) know the DB's structure, so I do hope OP doesn't construct his statements from user input. – Aaron Jun 01 '18 at 14:46

1 Answers1

3

You can't collect after .forEach() because it's a terminal operation with no return value (void) . Thus you can't collect. For it to work you can use the following snippet:

String sql = IntStream.range(0, selectFields.size())
    .mapToObj(i -> uuidFunction.get(i) != null 
         ? uuidFunction.get(i) + "(" + selectFields.get(i) + ")"
         : selectFields.get(i)
    )
    .collect(Collectors.joining(", ", "SELECT ", " FROM test_table");

Note that the joining overload with the 3 parameters has following signature:

public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                         CharSequence prefix,
                                                         CharSequence suffix){...}
Holger
  • 285,553
  • 42
  • 434
  • 765
Lino
  • 19,604
  • 6
  • 47
  • 65
  • It still can't get my head around it, but it might be just me (I'm no native english speaker). "[...]respectively are the prefix and suffix" might be better? – Aaron Jun 01 '18 at 14:00
  • @Aaron me neither. And It *does* sound weird. Have to rewrite that again – Lino Jun 01 '18 at 14:04
  • Well now there sure isn't any ambiguity left :) – Aaron Jun 01 '18 at 14:12
  • @Aaron Code speaks for itself, to much effort to try to explain a simple method signature ;) – Lino Jun 01 '18 at 14:14
  • @Holger doesn't work, because in that circumstance `i` is not known anymore – Lino Jun 01 '18 at 16:55
  • 1
    Oh right. I think, I'd still prefer less code duplication over simpler lambda, i.e. `.mapToObj(i -> { String u=uuidFunction.get(i), s=selectFields.get(i); return u!=null? u+'('+s+')': s; })` – Holger Jun 03 '18 at 09:00
  • @Holger I do too, but I am not a big fan of lambdas with multiple lines. So I just leave it here as is :) – Lino Jun 04 '18 at 06:17