diff --git a/deparser/jsqlparser/pom.xml b/deparser/jsqlparser/pom.xml index 5415803..aef19a3 100644 --- a/deparser/jsqlparser/pom.xml +++ b/deparser/jsqlparser/pom.xml @@ -45,5 +45,9 @@ jsqlparser 5.3.167 + + org.osgi + org.osgi.service.component.annotations + diff --git a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectDeparser.java b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectDeparser.java index dc41364..77cd152 100644 --- a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectDeparser.java +++ b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectDeparser.java @@ -23,8 +23,13 @@ /** * Factory implementation for creating dialect-aware SQL deparsers. + * + *

Identifiers in the AST are expected to already be in their canonical form + * (resolved against the database catalog by the caller). The deparser then + * always wraps them in the dialect's quote character so the resulting SQL is + * resolved by the engine via case-sensitive lookup. */ -@Component(service = DialectDeparser.class,scope = ServiceScope.SINGLETON) +@Component(service = DialectDeparser.class, scope = ServiceScope.SINGLETON) public class BasicDialectDeparser implements DialectDeparser { @Override diff --git a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectExpressionDeParser.java b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectExpressionDeParser.java index 5e63603..2c2f7ba 100644 --- a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectExpressionDeParser.java +++ b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectExpressionDeParser.java @@ -13,7 +13,12 @@ */ package org.eclipse.daanse.sql.deparser.jsqlparser; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Set; + import org.eclipse.daanse.jdbc.db.dialect.api.Dialect; +import org.eclipse.daanse.jdbc.db.dialect.api.IdentifierQuotingPolicy; import net.sf.jsqlparser.expression.DateValue; import net.sf.jsqlparser.expression.DoubleValue; @@ -29,6 +34,7 @@ public class BasicDialectExpressionDeParser extends ExpressionDeParser { private final Dialect dialect; + private final Deque> aliasScopes = new ArrayDeque<>(); public BasicDialectExpressionDeParser(Dialect dialect) { super(); @@ -42,26 +48,55 @@ public BasicDialectExpressionDeParser(SelectVisitor selectVisitor this.dialect = dialect; } + // Alias scope management — populated by BasicDialectSelectDeParser per PlainSelect. + + void pushAliasScope(Set aliases) { + aliasScopes.push(aliases); + } + + void popAliasScope() { + aliasScopes.pop(); + } + + private boolean isDeclaredAlias(String name) { + for (Set scope : aliasScopes) { + if (scope.contains(name)) { + return true; + } + } + return false; + } + // Identifier Quoting @Override public StringBuilder visit(Column tableColumn, S context) { final Table table = tableColumn.getTable(); - String tableName = null; if (table != null) { - if (table.getAlias() != null) { - tableName = table.getAlias().getName(); - } else { - tableName = table.getFullyQualifiedName(); + String name = table.getName(); + if (name != null && !name.isEmpty()) { + if (isDeclaredAlias(name)) { + // Alias declared in an enclosing FROM scope — emit verbatim so it + // matches the unquoted alias declaration (case-folding compatible). + builder.append(name).append('.'); + } else { + String catalog = table.getDatabaseName(); + String schema = table.getSchemaName(); + if (catalog != null && !catalog.isEmpty()) { + dialect.quoteIdentifierWith(catalog, builder, IdentifierQuotingPolicy.ALWAYS); + builder.append('.'); + } + if (schema != null && !schema.isEmpty()) { + dialect.quoteIdentifierWith(schema, builder, IdentifierQuotingPolicy.ALWAYS); + builder.append('.'); + } + dialect.quoteIdentifierWith(name, builder, IdentifierQuotingPolicy.ALWAYS); + builder.append('.'); + } } } - - if (tableName != null && !tableName.isEmpty()) { - dialect.quoteIdentifier(builder, tableName, tableColumn.getColumnName()); - } else { - dialect.quoteIdentifier(builder, tableColumn.getColumnName()); - } + dialect.quoteIdentifierWith(tableColumn.getColumnName(), builder, IdentifierQuotingPolicy.ALWAYS); if (tableColumn.getArrayConstructor() != null) { tableColumn.getArrayConstructor().accept(this, context); diff --git a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectSelectDeParser.java b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectSelectDeParser.java index 3d02084..ee801b4 100644 --- a/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectSelectDeParser.java +++ b/deparser/jsqlparser/src/main/java/org/eclipse/daanse/sql/deparser/jsqlparser/BasicDialectSelectDeParser.java @@ -13,11 +13,18 @@ */ package org.eclipse.daanse.sql.deparser.jsqlparser; +import java.util.HashSet; +import java.util.Set; + import org.eclipse.daanse.jdbc.db.dialect.api.Dialect; +import org.eclipse.daanse.jdbc.db.dialect.api.IdentifierQuotingPolicy; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.select.FromItem; +import net.sf.jsqlparser.statement.select.Join; +import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectItem; import net.sf.jsqlparser.util.deparser.SelectDeParser; @@ -36,6 +43,45 @@ public BasicDialectSelectDeParser(ExpressionVisitor expressionVis this.dialect = dialect; } + @Override + public StringBuilder visit(PlainSelect plainSelect, S context) { + Set aliases = collectAliases(plainSelect); + BasicDialectExpressionDeParser exprDeParser = currentExpressionDeParser(); + if (exprDeParser != null) { + exprDeParser.pushAliasScope(aliases); + try { + return super.visit(plainSelect, context); + } finally { + exprDeParser.popAliasScope(); + } + } + return super.visit(plainSelect, context); + } + + private BasicDialectExpressionDeParser currentExpressionDeParser() { + ExpressionVisitor visitor = getExpressionVisitor(); + return visitor instanceof BasicDialectExpressionDeParser + ? (BasicDialectExpressionDeParser) visitor + : null; + } + + private static Set collectAliases(PlainSelect ps) { + Set out = new HashSet<>(); + addAlias(out, ps.getFromItem()); + if (ps.getJoins() != null) { + for (Join j : ps.getJoins()) { + addAlias(out, j.getFromItem()); + } + } + return out; + } + + private static void addAlias(Set out, FromItem fi) { + if (fi != null && fi.getAlias() != null) { + out.add(fi.getAlias().getName()); + } + } + @Override public StringBuilder visit(Table table, S context) { // Quote catalog, schema, and table name @@ -44,17 +90,17 @@ public StringBuilder visit(Table table, S context) { String tableName = table.getName(); if (catalog != null && !catalog.isEmpty()) { - dialect.quoteIdentifier(builder, catalog); + dialect.quoteIdentifierWith(catalog, builder, IdentifierQuotingPolicy.ALWAYS); builder.append("."); } if (schema != null && !schema.isEmpty()) { - dialect.quoteIdentifier(builder, schema); + dialect.quoteIdentifierWith(schema, builder, IdentifierQuotingPolicy.ALWAYS); builder.append("."); } if (tableName != null) { - dialect.quoteIdentifier(builder, tableName); + dialect.quoteIdentifierWith(tableName, builder, IdentifierQuotingPolicy.ALWAYS); } // Handle table alias diff --git a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/BaiscDialectExpressionDeParserTest.java b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/BaiscDialectExpressionDeParserTest.java index eba899e..65a2a54 100644 --- a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/BaiscDialectExpressionDeParserTest.java +++ b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/BaiscDialectExpressionDeParserTest.java @@ -71,6 +71,7 @@ void testColumnWithTable_AnsiDialect() { Column column = new Column(table, "columnName"); deparser.visit(column, null); + // Real table name (no alias scope) is quoted alongside the column name. assertThat(deparser.getBuilder().toString()).isEqualTo("\"tableName\".\"columnName\""); } @@ -86,20 +87,6 @@ void testColumnWithTable_MySqlDialect() { assertThat(deparser.getBuilder().toString()).isEqualTo("`tableName`.`columnName`"); } - @Test - void testColumnWithTableAlias() { - Dialect dialect = MockDialectHelper.createAnsiDialect(); - BasicDialectExpressionDeParser deparser = new BasicDialectExpressionDeParser(dialect); - - Table table = new Table("tableName"); - table.setAlias(new net.sf.jsqlparser.expression.Alias("t")); - Column column = new Column(table, "columnName"); - deparser.visit(column, null); - - // When table has alias, use alias name - assertThat(deparser.getBuilder().toString()).isEqualTo("\"t\".\"columnName\""); - } - @Test void testColumnWithSchemaAndTable() { Dialect dialect = MockDialectHelper.createAnsiDialect(); @@ -109,8 +96,8 @@ void testColumnWithSchemaAndTable() { Column column = new Column(table, "columnName"); deparser.visit(column, null); - // Should use fully qualified name from table - assertThat(deparser.getBuilder().toString()).isEqualTo("\"schemaName.tableName\".\"columnName\""); + // Each FQN segment is quoted independently when the qualifier is a real name. + assertThat(deparser.getBuilder().toString()).isEqualTo("\"schemaName\".\"tableName\".\"columnName\""); } @Test diff --git a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/DialectStatementDeParserTest.java b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/DialectStatementDeParserTest.java index 636bef8..b28f02f 100644 --- a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/DialectStatementDeParserTest.java +++ b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/DialectStatementDeParserTest.java @@ -34,8 +34,9 @@ void testSimpleSelect_AnsiDialect() throws JSQLParserException { stmt.accept(deparser); String result = buffer.toString(); - // The column and table names should be quoted + // Identifiers are always quoted assertThat(result).contains("\"col1\""); + assertThat(result).contains("\"table1\""); } @Test @@ -48,8 +49,9 @@ void testSimpleSelect_MySqlDialect() throws JSQLParserException { stmt.accept(deparser); String result = buffer.toString(); - // MySQL uses backticks + // MySQL uses backticks; identifiers always quoted assertThat(result).contains("`col1`"); + assertThat(result).contains("`table1`"); } @Test @@ -89,9 +91,15 @@ void testSelectWithJoin() throws JSQLParserException { stmt.accept(deparser); String result = buffer.toString(); - // Columns with table aliases should be quoted - assertThat(result).contains("\"t1\""); - assertThat(result).contains("\"col1\""); + // Aliases used as qualifiers must remain unquoted; FROM table names are quoted + assertThat(result).contains("t1.\"col1\""); + assertThat(result).contains("t2.\"col2\""); + assertThat(result).contains("\"table1\""); + assertThat(result).contains("\"table2\""); + assertThat(result).contains("AS t1"); + assertThat(result).contains("AS t2"); + assertThat(result).doesNotContain("\"t1\""); + assertThat(result).doesNotContain("\"t2\""); } @Test @@ -107,4 +115,82 @@ void testSelectWithSubquery() throws JSQLParserException { assertThat(result).contains("\"id\""); } + /** + * Regression: when a column reference is qualified by a real (unaliased) table + * name, the qualifier must be quoted just like the column name. Otherwise a + * case-folding engine like H2 will fail to resolve mixed-case real names. + */ + @Test + void testQualifiedColumnQualifierWithRealTableName_IsQuoted() throws JSQLParserException { + Dialect dialect = MockDialectHelper.createAnsiDialect(); + StringBuilder buffer = new StringBuilder(); + BasicDialectStatementDeParser deparser = new BasicDialectStatementDeParser(buffer, dialect); + + Statement stmt = CCJSqlParserUtil.parse( + "SELECT ProductCategory.EnglishProductCategoryName, sum(Fact.OrderQuantity) " + + "FROM Fact JOIN ProductCategory ON Fact.cat = ProductCategory.cat"); + stmt.accept(deparser); + + String result = buffer.toString(); + assertThat(result).contains("\"Fact\".\"OrderQuantity\""); + assertThat(result).contains("\"ProductCategory\".\"EnglishProductCategoryName\""); + assertThat(result).contains("\"Fact\".\"cat\""); + assertThat(result).contains("\"ProductCategory\".\"cat\""); + assertThat(result).doesNotContain("Fact.\""); + assertThat(result).doesNotContain("ProductCategory.\""); + } + + /** + * Regression: when one table is aliased and another isn't, the alias qualifier + * stays unquoted and the real table-name qualifier gets quoted. + */ + @Test + void testMixedAliasAndRealTableQualifiers() throws JSQLParserException { + Dialect dialect = MockDialectHelper.createAnsiDialect(); + StringBuilder buffer = new StringBuilder(); + BasicDialectStatementDeParser deparser = new BasicDialectStatementDeParser(buffer, dialect); + + Statement stmt = CCJSqlParserUtil.parse( + "SELECT f.col1, ProductCategory.col2 " + + "FROM Fact f JOIN ProductCategory ON f.cat = ProductCategory.cat"); + stmt.accept(deparser); + + String result = buffer.toString(); + // Alias qualifier verbatim, real table qualifier quoted + assertThat(result).contains("f.\"col1\""); + assertThat(result).contains("\"ProductCategory\".\"col2\""); + assertThat(result).contains("f.\"cat\""); + assertThat(result).contains("\"ProductCategory\".\"cat\""); + assertThat(result).doesNotContain("\"f\"."); + } + + /** + * Regression for the original H2 bug: a column reference's table-qualifier + * must be emitted verbatim so it matches the unquoted alias declared in the + * FROM clause. Mixed-case real table/column names are quoted as usual. + */ + @Test + void testQualifiedColumnQualifierIsNotQuoted_H2Compatible() throws JSQLParserException { + Dialect dialect = MockDialectHelper.createAnsiDialect(); + StringBuilder buffer = new StringBuilder(); + BasicDialectStatementDeParser deparser = new BasicDialectStatementDeParser(buffer, dialect); + + Statement stmt = CCJSqlParserUtil.parse( + "SELECT pc.EnglishProductCategoryName, sum(f.OrderQuantity) " + + "FROM Fact f JOIN ProductCategory pc ON f.cat = pc.cat"); + stmt.accept(deparser); + + String result = buffer.toString(); + // Column names always quoted + assertThat(result).contains("f.\"OrderQuantity\""); + assertThat(result).contains("pc.\"EnglishProductCategoryName\""); + assertThat(result).contains("f.\"cat\""); + assertThat(result).contains("pc.\"cat\""); + // Aliases as qualifiers stay unquoted + assertThat(result).contains("AS f"); + assertThat(result).contains("AS pc"); + assertThat(result).doesNotContain("\"f\"."); + assertThat(result).doesNotContain("\"pc\"."); + } + } diff --git a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/MockDialectHelper.java b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/MockDialectHelper.java index 32d8778..9645547 100644 --- a/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/MockDialectHelper.java +++ b/deparser/jsqlparser/src/test/java/org/eclipse/daanse/sql/deparser/jsqlparser/MockDialectHelper.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import org.eclipse.daanse.jdbc.db.dialect.api.Dialect; +import org.eclipse.daanse.jdbc.db.dialect.api.IdentifierQuotingPolicy; public class MockDialectHelper { @@ -34,48 +35,19 @@ public static Dialect createMySqlDialect() { public static Dialect createSqlServerDialect() { Dialect dialect = mock(Dialect.class); - when(dialect.getQuoteIdentifierString()).thenReturn("["); - - when(dialect.quoteIdentifier(any(CharSequence.class))).thenAnswer(inv -> { - CharSequence val = inv.getArgument(0); - return new StringBuilder("[").append(val).append("]"); - }); - doAnswer(inv -> { String val = inv.getArgument(0); StringBuilder buf = inv.getArgument(1); - if (val != null) { + IdentifierQuotingPolicy policy = inv.getArgument(2); + if (val == null) + return null; + if (policy == IdentifierQuotingPolicy.NEVER) { + buf.append(val); + } else { buf.append("[").append(val).append("]"); } return null; - }).when(dialect).quoteIdentifier(anyString(), any(StringBuilder.class)); - - when(dialect.quoteIdentifier(anyString(), anyString())).thenAnswer(inv -> { - String qual = inv.getArgument(0); - String name = inv.getArgument(1); - StringBuilder sb = new StringBuilder(); - if (qual != null) { - sb.append("[").append(qual).append("]."); - } - sb.append("[").append(name).append("]"); - return sb.toString(); - }); - - doAnswer(inv -> { - StringBuilder buf = inv.getArgument(0); - Object[] args = inv.getArguments(); - boolean first = true; - for (int i = 1; i < args.length; i++) { - String name = (String) args[i]; - if (name == null) - continue; - if (!first) - buf.append("."); - buf.append("[").append(name).append("]"); - first = false; - } - return null; - }).when(dialect).quoteIdentifier(any(StringBuilder.class), (String[]) any()); + }).when(dialect).quoteIdentifierWith(anyString(), any(StringBuilder.class), any(IdentifierQuotingPolicy.class)); doAnswer(inv -> { StringBuilder buf = inv.getArgument(0); @@ -115,7 +87,6 @@ public static Dialect createSqlServerDialect() { when(dialect.allowsFromAlias()).thenReturn(true); when(dialect.allowsFieldAlias()).thenReturn(true); when(dialect.needsExponent(any(), anyString())).thenReturn(false); - when(dialect.name()).thenReturn("sqlserver"); return dialect; } @@ -123,48 +94,19 @@ public static Dialect createSqlServerDialect() { public static Dialect createDialectWithQuote(String quoteChar) { Dialect dialect = mock(Dialect.class); - when(dialect.getQuoteIdentifierString()).thenReturn(quoteChar); - - when(dialect.quoteIdentifier(any(CharSequence.class))).thenAnswer(inv -> { - CharSequence val = inv.getArgument(0); - return new StringBuilder(quoteChar).append(val).append(quoteChar); - }); - doAnswer(inv -> { String val = inv.getArgument(0); StringBuilder buf = inv.getArgument(1); - if (val != null) { + IdentifierQuotingPolicy policy = inv.getArgument(2); + if (val == null) + return null; + if (policy == IdentifierQuotingPolicy.NEVER) { + buf.append(val); + } else { buf.append(quoteChar).append(val).append(quoteChar); } return null; - }).when(dialect).quoteIdentifier(anyString(), any(StringBuilder.class)); - - when(dialect.quoteIdentifier(anyString(), anyString())).thenAnswer(inv -> { - String qual = inv.getArgument(0); - String name = inv.getArgument(1); - StringBuilder sb = new StringBuilder(); - if (qual != null) { - sb.append(quoteChar).append(qual).append(quoteChar).append("."); - } - sb.append(quoteChar).append(name).append(quoteChar); - return sb.toString(); - }); - - doAnswer(inv -> { - StringBuilder buf = inv.getArgument(0); - Object[] args = inv.getArguments(); - boolean first = true; - for (int i = 1; i < args.length; i++) { - String name = (String) args[i]; - if (name == null) - continue; - if (!first) - buf.append("."); - buf.append(quoteChar).append(name).append(quoteChar); - first = false; - } - return null; - }).when(dialect).quoteIdentifier(any(StringBuilder.class), (String[]) any()); + }).when(dialect).quoteIdentifierWith(anyString(), any(StringBuilder.class), any(IdentifierQuotingPolicy.class)); doAnswer(inv -> { StringBuilder buf = inv.getArgument(0); @@ -204,7 +146,6 @@ public static Dialect createDialectWithQuote(String quoteChar) { when(dialect.allowsFromAlias()).thenReturn(true); when(dialect.allowsFieldAlias()).thenReturn(true); when(dialect.needsExponent(any(), anyString())).thenReturn(false); - when(dialect.name()).thenReturn("mock"); return dialect; } diff --git a/guard/jsqltranspiler/pom.xml b/guard/jsqltranspiler/pom.xml index 42e116c..2fe318a 100644 --- a/guard/jsqltranspiler/pom.xml +++ b/guard/jsqltranspiler/pom.xml @@ -52,5 +52,17 @@ 0.0.1-SNAPSHOT test + + org.eclipse.daanse + org.eclipse.daanse.jdbc.db.dialect.db.h2 + 0.0.1-SNAPSHOT + test + + + org.eclipse.daanse + org.eclipse.daanse.jdbc.db.dialect.db.common + 0.0.1-SNAPSHOT + test + diff --git a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/DeparserColumResolver.java b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/DeparserColumResolver.java index 64bea82..c160b1a 100644 --- a/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/DeparserColumResolver.java +++ b/guard/jsqltranspiler/src/main/java/org/eclipse/daanse/sql/guard/jsqltranspiler/DeparserColumResolver.java @@ -46,7 +46,7 @@ public String getResolvedStatementText(String sqlStr) throws JSQLParserException select.accept((SelectVisitor) this, JdbcMetaData.copyOf(metaData)); } - return dialectDeparser.deparse(st, dialect); + return dialectDeparser.deparse(st, dialect); } } diff --git a/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java b/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java index bc90673..1b194d0 100644 --- a/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java +++ b/guard/jsqltranspiler/src/test/java/org/eclipse/daanse/sql/guard/jsqltranspiler/integration/SqlGuardTest.java @@ -16,15 +16,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import org.mockito.ArgumentMatchers; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.List; import org.eclipse.daanse.jdbc.db.dialect.api.Dialect; +import org.eclipse.daanse.jdbc.db.dialect.db.h2.H2Dialect; import org.eclipse.daanse.sql.guard.api.SqlGuard; import org.eclipse.daanse.sql.guard.api.SqlGuardFactory; import org.eclipse.daanse.sql.guard.api.elements.DatabaseCatalog; @@ -42,8 +40,6 @@ import org.junit.jupiter.params.provider.ValueSource; import java.util.stream.Stream; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.osgi.test.common.annotation.InjectService; public class SqlGuardTest { @@ -72,13 +68,13 @@ public class SqlGuardTest { private static final String SQL_WITH_ALLOWED_FUNCTION_IN_HAVING = "select foo.name from foo group by foo.name HAVING %s(foo.id) > 5"; - private static final String SQL_WITH_FUNCTION_EXPECTED = "SELECT Trim( foo.name ) FROM sch.foo"; + private static final String SQL_WITH_FUNCTION_EXPECTED = "SELECT Trim( \"foo\".\"name\" ) FROM \"sch\".\"foo\""; - private static final String SQL_WITH_ALLOWED_FUNCTION_EXPECTED = "SELECT %s(foo.name) FROM sch.foo"; + private static final String SQL_WITH_ALLOWED_FUNCTION_EXPECTED = "SELECT %s(\"foo\".\"name\") FROM \"sch\".\"foo\""; - private static final String SQL_WITH_ALLOWED_FUNCTION__IN_WHERE_EXPECTED = "SELECT foo.name FROM sch.foo WHERE %s(foo.name) = 1"; + private static final String SQL_WITH_ALLOWED_FUNCTION__IN_WHERE_EXPECTED = "SELECT \"foo\".\"name\" FROM \"sch\".\"foo\" WHERE %s(\"foo\".\"name\") = 1"; - private static final String SQL_WITH_ALLOWED_FUNCTION__IN_HAVING_EXPECTED = "SELECT foo.name FROM sch.foo GROUP BY foo.name HAVING %s(foo.id) > 5"; + private static final String SQL_WITH_ALLOWED_FUNCTION__IN_HAVING_EXPECTED = "SELECT \"foo\".\"name\" FROM \"sch\".\"foo\" GROUP BY \"foo\".\"name\" HAVING %s(\"foo\".\"id\") > 5"; private static final String SQL_WITH_HAVING_WRONG_COLUMN = """ select %s(foo.id) from foo group by foo.name having foo.name1 = 'tets'"""; @@ -90,13 +86,13 @@ public class SqlGuardTest { select %s(foo.id) from foo group by foo.name having foo.name = 'tets'"""; private static final String SQL_WITH_HAVING1_EXPECTED = """ - SELECT %s(foo.id) FROM sch.foo GROUP BY foo.name HAVING foo.name = 'tets'"""; + SELECT %s("foo"."id") FROM "sch"."foo" GROUP BY "foo"."name" HAVING "foo"."name" = 'tets'"""; private static final String SQL_WITH_HAVING = """ select %s(foo.id) from foo group by foo.name having %s(foo.id) > 5"""; private static final String SQL_WITH_HAVING_EXPECTED = """ - SELECT %s(foo.id) FROM sch.foo GROUP BY foo.name HAVING %s(foo.id) > 5"""; + SELECT %s("foo"."id") FROM "sch"."foo" GROUP BY "foo"."name" HAVING %s("foo"."id") > 5"""; private static final String SQL_WITH_HAVING_WRONG_TABLE = """ select %s(foo.id) from foo group by foo.name having %s(foo1.id) > 5"""; @@ -108,12 +104,12 @@ public class SqlGuardTest { select %s(foo.id) from foo group by foo.name"""; private static final String SQL_WITH_AGG_EXPECTED = """ - SELECT %s(foo.id) FROM sch.foo GROUP BY foo.name"""; + SELECT %s("foo"."id") FROM "sch"."foo" GROUP BY "foo"."name\""""; private static final String SQL_WITH_GROUP = "select * from foo group by foo.id, foo.name"; private static final String SQL_WITH_GROUP_EXPECTED = """ - SELECT foo.id, foo.name FROM sch.foo GROUP BY foo.id, foo.name"""; + SELECT "foo"."id", "foo"."name" FROM "sch"."foo" GROUP BY "foo"."id", "foo"."name\""""; private static final String TABLE_FOO1_DOES_NOT_EXIST_IN_THE_GIVEN_SCHEMA_SCH = "Table foo1 not found in schema []"; @@ -127,42 +123,42 @@ public class SqlGuardTest { select *, 5 as testColumn from foo where foo.id = 10"""; private static final String SQL_WITH_CUSTOM_COLUMN_EXPECTED = """ - SELECT foo.id, foo.name, 5 testColumn FROM sch.foo WHERE foo.id = 10"""; + SELECT "foo"."id", "foo"."name", 5 AS testColumn FROM "sch"."foo" WHERE "foo"."id" = 10"""; private static final String SQL_WITH_IN = """ select * from foo where foo.id in (select fooFact.id from fooFact)"""; private static final String SQL_WITH_IN_EXPECTED = """ - SELECT foo.id, foo.name FROM sch.foo WHERE foo.id IN (SELECT fooFact.id FROM fooFact)"""; + SELECT "foo"."id", "foo"."name" FROM "sch"."foo" WHERE "foo"."id" IN (SELECT "fooFact"."id" FROM "fooFact")"""; private static final String TRIPLE_SELECT_SQL = """ SELECT * FROM ( SELECT * FROM ( SELECT * FROM foo inner join fooFact on foo.id = fooFact.id ) a ) b"""; private static final String TRIPLE_SELECT_SQL_EXPECTED = """ - SELECT b.id, b.name, b.id_1, b.value FROM (SELECT a.id, a.name, a.id_1, a.value FROM (SELECT foo.id, foo.name, fooFact.id, fooFact.value FROM sch.foo INNER JOIN sch.fooFact ON foo.id = fooFact.id) a) b"""; + SELECT b."id", b."name", b."id_1", b."value" FROM (SELECT a."id", a."name", a."id_1", a."value" FROM (SELECT "foo"."id", "foo"."name", "fooFact"."id", "fooFact"."value" FROM "sch"."foo" INNER JOIN "sch"."fooFact" ON "foo"."id" = "fooFact"."id") a) b"""; private static final String SELECT_INNER_JOIN_C_D = """ SELECT * FROM ((SELECT * FROM foo) c inner join fooFact on c.id = fooFact.id ) d"""; private static final String SELECT_INNER_JOIN_C_D_EXPECTED = """ - SELECT d.id, d.name, d.id_1, d.value FROM ((SELECT foo.id, foo.name FROM sch.foo) c INNER JOIN sch.fooFact ON c.id = fooFact.id) d"""; + SELECT d."id", d."name", d."id_1", d."value" FROM ((SELECT "foo"."id", "foo"."name" FROM "sch"."foo") c INNER JOIN sch.fooFact ON c.id = fooFact.id) d"""; private static final String SELECT_INNER_JOIN_D = """ SELECT * FROM ( SELECT * FROM foo inner join fooFact on foo.id = fooFact.id ) d"""; private static final String SELECT_INNER_JOIN_D_EXPECTED = """ - SELECT d.id, d.name, d.id_1, d.value FROM (SELECT foo.id, foo.name, fooFact.id, fooFact.value FROM sch.foo INNER JOIN sch.fooFact ON foo.id = fooFact.id) d"""; + SELECT d."id", d."name", d."id_1", d."value" FROM (SELECT "foo"."id", "foo"."name", "fooFact"."id", "fooFact"."value" FROM "sch"."foo" INNER JOIN "sch"."fooFact" ON "foo"."id" = "fooFact"."id") d"""; private static final String SELECT_INNER_JOIN = """ select * from foo inner join fooFact on foo.id = fooFact.id"""; private static final String SELECT_INNER_JOIN_EXPECTED = """ - SELECT foo.id, foo.name, fooFact.id, fooFact.value FROM sch.foo INNER JOIN sch.fooFact ON foo.id = fooFact.id"""; + SELECT "foo"."id", "foo"."name", "fooFact"."id", "fooFact"."value" FROM "sch"."foo" INNER JOIN "sch"."fooFact" ON "foo"."id" = "fooFact"."id\""""; private static final String SELECT_FROM_FOO = "select * from foo"; private static final String SELECT_FROM_FOO_RESULT = """ - SELECT foo.id, foo.name FROM sch.foo"""; + SELECT "foo"."id", "foo"."name" FROM "sch"."foo\""""; private static final List AGGREGATIONS = List.of("sum", "count", "distinctcount", "avg"); @@ -343,53 +339,7 @@ WHERE foo.id IN (SELECT fooFact.id FROM fooFact) @BeforeAll public static void setUp() { - dialect = mock(Dialect.class); - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - Object[] arguments = invocation.getArguments(); - - if (arguments != null && arguments.length >= 2 && arguments[0] != null) { - StringBuilder sb = (StringBuilder) arguments[0]; - // Handle varargs - remaining arguments are the identifier parts - boolean first = true; - for (int i = 1; i < arguments.length; i++) { - if (arguments[i] != null) { - if (!first) { - sb.append("."); - } - sb.append((String) arguments[i]); - first = false; - } - } - } - return null; - } - }).when(dialect).quoteIdentifier(any(StringBuilder.class), ArgumentMatchers.any()); - - // Mock quoteNumericLiteral to just append the value - doAnswer(invocation -> { - StringBuilder sb = (StringBuilder) invocation.getArgument(0); - String value = (String) invocation.getArgument(1); - sb.append(value); - return null; - }).when(dialect).quoteNumericLiteral(any(StringBuilder.class), any(String.class)); - - // Mock quoteStringLiteral to append quoted value - doAnswer(invocation -> { - StringBuilder sb = (StringBuilder) invocation.getArgument(0); - String value = (String) invocation.getArgument(1); - sb.append("'").append(value).append("'"); - return null; - }).when(dialect).quoteStringLiteral(any(StringBuilder.class), any(String.class)); - - // Mock quoteBooleanLiteral - doAnswer(invocation -> { - StringBuilder sb = (StringBuilder) invocation.getArgument(0); - String value = (String) invocation.getArgument(1); - sb.append(value); - return null; - }).when(dialect).quoteBooleanLiteral(any(StringBuilder.class), any(String.class)); + dialect = new H2Dialect(); } @Nested diff --git a/guard/jsqltranspiler/test.bndrun b/guard/jsqltranspiler/test.bndrun index 7d6f7cc..a9fefab 100644 --- a/guard/jsqltranspiler/test.bndrun +++ b/guard/jsqltranspiler/test.bndrun @@ -66,6 +66,9 @@ org.apache.felix.scr;version='[2.2.10,2.2.11)',\ org.eclipse.daanse.jdbc.db.api;version='[0.0.1,0.0.2)',\ org.eclipse.daanse.jdbc.db.dialect.api;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.jdbc.db.dialect.db.common;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.jdbc.db.dialect.db.h2;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.jdbc.db.record;version='[0.0.1,0.0.2)',\ org.eclipse.daanse.sql.deparser.api;version='[0.0.1,0.0.2)',\ org.eclipse.daanse.sql.deparser.jsqlparser;version='[0.0.1,0.0.2)',\ org.eclipse.daanse.sql.guard.api;version='[0.0.1,0.0.2)',\