|
| 1 | +package misk.jooq.listeners |
| 2 | + |
| 3 | +import org.jooq.ExecuteContext |
| 4 | +import org.jooq.impl.DefaultExecuteListener |
| 5 | + |
| 6 | +class AvoidUsingSelectStarListener : DefaultExecuteListener() { |
| 7 | + |
| 8 | + /** |
| 9 | + * This catches any query that has a select * from or select table.* from. |
| 10 | + * We don't want to use any query that uses a select * in it, as jooq has a hard time converting |
| 11 | + * the result set into a jooq table record. It captures the result set via indexes and not the |
| 12 | + * column names. If you try to fetch the result set into a jooq record, jooq will expect the order |
| 13 | + * in which the columns are returned in the query matches the order of in which the columns are |
| 14 | + * declared in the jooq generated code. |
| 15 | + * I suppose it does ResultSet.get(0), ResulSet.get(1) instead of doing ResultSet.get(<column name) |
| 16 | + * |
| 17 | + * If the databases in dev, staging and prod don't all have the same column ordering, then things |
| 18 | + * start to fail. |
| 19 | + * |
| 20 | + * Either way from a code maintainability point of view it is best to avoid `select * from` and |
| 21 | + * always specify the columns you need. If you need all the columns in a table 2 ways of doing that |
| 22 | + * in jooq |
| 23 | + * ``` |
| 24 | + * ctx.selectFrom(<table name>)... |
| 25 | + * ``` |
| 26 | + * If you are joining multiple tables and need the columns of only one table |
| 27 | + * |
| 28 | + * ``` |
| 29 | + * ctx.select(<jooq gen table>.fields().toList()).from(<table>.innerJoin....) |
| 30 | + * ``` |
| 31 | + * |
| 32 | + * DO NOT DO THIS: |
| 33 | + * ``` |
| 34 | + * ctx.select(<jooq gen table>.asterisk()).from(<table>)... |
| 35 | + * ``` |
| 36 | + * This listener's purpose is to catch the above and prevent it from happening. |
| 37 | + */ |
| 38 | + override fun renderEnd(ctx: ExecuteContext?) { |
| 39 | + if (ctx?.sql()?.matches(selectStarFromRegex) == true) { |
| 40 | + throw AvoidUsingSelectStarException( |
| 41 | + "Do not use select * from. " + |
| 42 | + "Please read the docs of class AvoidUsingSelectStartListener#renderEnd to learn more. " + |
| 43 | + "Generated [sql=${ctx.sql()}" |
| 44 | + ) |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + companion object { |
| 49 | + val selectStarFromRegex = Regex( |
| 50 | + "select(.*?\\.\\*.*?)from.*", |
| 51 | + setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE) |
| 52 | + ) |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +class AvoidUsingSelectStarException(message: String) : RuntimeException(message) {} |
0 commit comments