From 0b6e3f6d83eca01bda9a96c8fa8103eb0afc7ae0 Mon Sep 17 00:00:00 2001 From: Hideki Sugimoto Date: Sat, 4 Nov 2023 16:10:54 +0900 Subject: [PATCH] Improved performance of comma and AND/OR removal regular expressions. --- .../uroborosql/context/SqlContextImpl.java | 17 ++- .../context/SqlContextImplTest.java | 101 ++++++++++++++++-- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/src/main/java/jp/co/future/uroborosql/context/SqlContextImpl.java b/src/main/java/jp/co/future/uroborosql/context/SqlContextImpl.java index 564e3f95..cc96f750 100644 --- a/src/main/java/jp/co/future/uroborosql/context/SqlContextImpl.java +++ b/src/main/java/jp/co/future/uroborosql/context/SqlContextImpl.java @@ -75,13 +75,12 @@ public boolean contains(final Object o) { } /** where句の直後にくるANDやORを除外するための正規表現 */ - protected static final Pattern WHERE_CLAUSE_PATTERN = Pattern - .compile("(?i)(?(^|\\s+)(WHERE\\s+(--.*|/\\*[^(/\\*|\\*/)]+?\\*/\\s*)*\\s*))(AND\\s+|OR\\s+)"); + protected static final Pattern WHERE_CLAUSE_PATTERN = Pattern.compile( + "(?i)(?(\\bWHERE\\s+(--.*|/\\*[^(/\\*|\\*/)]+?\\*/\\s*)*\\s*))((AND|OR)\\s+)"); /** 各句の最初に現れるカンマを除去するための正規表現 */ - protected static final Pattern REMOVE_FIRST_COMMA_PATTERN = Pattern - .compile( - "(?i)(?((^|\\s+)(SELECT|ORDER\\s+BY|GROUP\\s+BY|SET)\\s+|\\(\\s*)(--.*|/\\*[^(/\\*|\\*/)]+?\\*/\\s*)*\\s*)(,)"); + protected static final Pattern REMOVE_FIRST_COMMA_PATTERN = Pattern.compile( + "(?i)(?(\\b(SELECT|ORDER\\s+BY|GROUP\\s+BY|SET)\\s+|\\(\\s*)(--.*|/\\*[^(/\\*|\\*/)]+?\\*/\\s*)*\\s*),"); /** 不要な空白、改行を除去するための正規表現 */ protected static final Pattern CLEAR_BLANK_PATTERN = Pattern.compile("(?m)^\\s*(\\r\\n|\\r|\\n)"); @@ -327,7 +326,7 @@ public String getSchema() { * @see jp.co.future.uroborosql.context.SqlContext#setSchema(java.lang.String) */ @Override - public SqlContext setSchema(String schema) { + public SqlContext setSchema(final String schema) { this.schema = schema; return this; } @@ -531,7 +530,7 @@ public SqlContext paramListIfAbsent(final String parameterName, @Override public SqlContext paramMap(final Map paramMap) { if (paramMap != null) { - paramMap.forEach((k, v) -> param(k, v)); + paramMap.forEach(this::param); } return this; } @@ -979,7 +978,7 @@ public SqlContext addBatch() { * @param baseSize 基底となるMapのサイズ * @return 初期容量 */ - private int calcInitialCapacity(int baseSize) { + private int calcInitialCapacity(final int baseSize) { // MapのloadFactorはデフォルト0.75(3/4)なので 4/3 を掛けてcapacityを計算する。そのうえで切り捨てが発生してもキャパシティを越えないよう +1 している。 return baseSize * 4 / 3 + 1; } @@ -1269,7 +1268,7 @@ public Function getUpdateDelegate() { * @see jp.co.future.uroborosql.context.SqlContext#setUpdateDelegate(java.util.function.Function) */ @Override - public SqlContext setUpdateDelegate(Function updateDelegate) { + public SqlContext setUpdateDelegate(final Function updateDelegate) { this.updateDelegate = updateDelegate; return this; } diff --git a/src/test/java/jp/co/future/uroborosql/context/SqlContextImplTest.java b/src/test/java/jp/co/future/uroborosql/context/SqlContextImplTest.java index 11b32471..9ad177eb 100644 --- a/src/test/java/jp/co/future/uroborosql/context/SqlContextImplTest.java +++ b/src/test/java/jp/co/future/uroborosql/context/SqlContextImplTest.java @@ -1,13 +1,23 @@ package jp.co.future.uroborosql.context; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.sql.JDBCType; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -17,6 +27,8 @@ import org.junit.BeforeClass; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jp.co.future.uroborosql.UroboroSQL; import jp.co.future.uroborosql.config.SqlConfig; @@ -27,6 +39,9 @@ import jp.co.future.uroborosql.parser.SqlParserImpl; public class SqlContextImplTest { + /** ロガー */ + protected static final Logger log = LoggerFactory.getLogger(SqlContextImplTest.class); + private static SqlConfig config = null; @BeforeClass @@ -42,7 +57,7 @@ private SqlContext getSqlContext(final String sql) { } private String replaceLineSep(final String sql) { - return sql.replaceAll("\\[LF\\]", System.lineSeparator()); + return sql.replace("[LF]", System.lineSeparator()); } @Test @@ -88,6 +103,17 @@ public void removeFirstAndKeyWordWhenWhereClause() throws Exception { SqlContext ctx62 = getSqlContext("select * from test[LF]where /* comment */ --comment [LF] order = 1"); assertEquals(replaceLineSep("select * from test[LF]where /* comment */ --comment [LF] order = 1"), ctx62.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep("select * from test[LF]where -- /* comment */ [LF] and aaa = 1"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstAndKeyWordWhenWhereClause elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); } @Test @@ -126,6 +152,18 @@ public void removeFirstCommaWhenSelectClause() throws Exception { assertEquals(replaceLineSep( "with dummy as ( select * from dummy )[LF]select /* コメント:japanese comment */ [LF] aaa[LF], bbb[LF], ccc from test"), ctx8.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep( + "with dummy as ( select * from dummy )[LF]select /* コメント:japanese comment */ [LF], aaa[LF], bbb[LF], ccc from test"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstCommaWhenSelectClause elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); } @Test @@ -165,6 +203,17 @@ public void removeFirstCommaWhenOrderByClause() throws Exception { SqlContext ctx62 = getSqlContext("select * from test[LF]order by --/* comment */[LF], aaa, bbb"); assertEquals(replaceLineSep("select * from test[LF]order by --/* comment */[LF] aaa, bbb"), ctx62.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep("select * from test[LF]order by --/* comment */[LF], aaa, bbb"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstCommaWhenOrderByClause elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); } @Test @@ -204,6 +253,18 @@ public void removeFirstCommaWhenGroupByClause() throws Exception { SqlContext ctx62 = getSqlContext("select * from test[LF]group by /* comment */ --aaa[LF], aaa, bbb"); assertEquals(replaceLineSep("select * from test[LF]group by /* comment */ --aaa[LF] aaa, bbb"), ctx62.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep("select * from test[LF]group by /* comment */ --aaa[LF], aaa, bbb"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstCommaWhenGroupByClause elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); + } @Test @@ -255,6 +316,18 @@ public void removeFirstCommaWhenStartBracket() throws Exception { replaceLineSep( "insert into[LF](--comment[LF] aaa[LF], bbb[LF], ccc[LF]) values (/*comment*/[LF]111,[LF]222,[LF]333[LF])"), ctx52.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep( + "insert into[LF](--comment[LF], aaa[LF], bbb[LF], ccc[LF]) values (,/*comment*/[LF]111,[LF]222,[LF]333[LF])"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstCommaWhenStartBracket elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); } @Test @@ -307,6 +380,17 @@ public void removeFirstCommaWhenSetClause() throws Exception { "select[LF], aaa,[LF]code_set,[LF]bbb,[LF]ccc[LF]from[LF]test[LF]where[LF]1 = 1"); assertEquals(replaceLineSep("select[LF] aaa,[LF]code_set,[LF]bbb,[LF]ccc[LF]from[LF]test[LF]where[LF]1 = 1"), ctx63.getExecutableSql()); + + Instant startTime = Instant.now(Clock.systemDefaultZone()); + String sql = replaceLineSep("select[LF], aaa,[LF]code_set,[LF]bbb,[LF]ccc[LF]from[LF]test[LF]where[LF]1 = 1"); + for (int i = 0; i < 1000000; i++) { + SqlContext timeCtx = config.contextWith(sql); + timeCtx.addSqlPart(sql); + timeCtx.getExecutableSql(); + } + log.info("removeFirstCommaWhenSetClause elapsed time. {}", + DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS").format( + LocalTime.MIDNIGHT.plus(Duration.between(startTime, Instant.now(Clock.systemDefaultZone()))))); } @Test @@ -333,9 +417,8 @@ public void testHasParam() throws Exception { @SuppressWarnings("deprecation") @Test public void testParamList() throws Exception { - SqlContext ctx = null; + SqlContext ctx = getSqlContext("select * from dummy"); - ctx = getSqlContext("select * from dummy"); ctx.paramList("key1", "value1"); assertThat(ctx.getParam("key1").getValue(), is(Arrays.asList("value1"))); @@ -352,18 +435,15 @@ public void testParamList() throws Exception { assertThat(ctx.getParam("key1").getValue(), is(values)); ctx = getSqlContext("select * from dummy"); - ctx.paramList("key1", () -> { - return values; - }); + ctx.paramList("key1", () -> values); assertThat(ctx.getParam("key1").getValue(), is(values)); } @SuppressWarnings("deprecation") @Test public void testIfAbsent() throws Exception { - SqlContext ctx = null; + SqlContext ctx = getSqlContext("select * from dummy"); - ctx = getSqlContext("select * from dummy"); ctx.paramIfAbsent("key1", "value1"); assertThat(ctx.getParam("key1").getValue(), is("value1")); ctx.paramIfAbsent("key1", "value2"); @@ -687,7 +767,6 @@ public static class TestEntity { private Optional memo = Optional.empty(); public TestEntity(final int id, final String name, final int age, final Optional memo) { - super(); this.id = id; this.name = name; this.age = age;