Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,42 @@ public StringBuilder generateKnownFunction(org.eclipse.daanse.sql.dialect.api.ge
case YEAR, MONTH, DAY, HOUR, MINUTE, SECOND -> arguments.size() == 1
? new StringBuilder(function.name()).append("(").append(arguments.get(0)).append(")")
: super.generateKnownFunction(function, arguments);
// Derby casts no number to VARCHAR ("Cannot convert types 'INTEGER' to 'VARCHAR'"), but to CHAR, which it pads
// Derby casts no number to VARCHAR ("Cannot convert types 'INTEGER' to 'VARCHAR'") and no DOUBLE or REAL to
// CHAR either (42846); its CHAR function takes every number, a DOUBLE as Java writes it (2.24, 17.0, 1.0E10),
// a DECIMAL with its scale (2.50), and pads the text
case TO_TEXT -> arguments.size() == 1
? new StringBuilder("TRIM(CAST(").append(arguments.get(0)).append(" AS CHAR(254)))")
? new StringBuilder("TRIM(CHAR(").append(arguments.get(0)).append("))")
: super.generateKnownFunction(function, arguments);
// Derby has no RPAD ("'RPAD' is not recognized as a function"): the text and as many spaces as the length,
// cut to the length
case PAD_RIGHT -> padRight(arguments);
// Derby casts no whole number to VARCHAR and has no LPAD: the ISO text yyyy-mm-dd from CHAR, a month and a day
// two digits wide as the last two of 100 more
case DATE_FROM_PARTS -> arguments.size() == 3
? new StringBuilder("CAST(TRIM(CHAR(").append(arguments.get(0)).append(")) || '-' || SUBSTR(TRIM(CHAR(100 + ")
.append(arguments.get(1)).append(")), 2) || '-' || SUBSTR(TRIM(CHAR(100 + ")
.append(arguments.get(2)).append(")), 2) AS DATE)")
: super.generateKnownFunction(function, arguments);
default -> super.generateKnownFunction(function, arguments);
};
}

private StringBuilder padRight(List<? extends CharSequence> arguments) {
if (arguments.size() != 2) {
return super.generateKnownFunction(org.eclipse.daanse.sql.dialect.api.generator.KnownFunction.PAD_RIGHT,
arguments);
}
int length;
try {
length = Integer.parseInt(arguments.get(1).toString().trim());
} catch (NumberFormatException notALiteral) {
return super.generateKnownFunction(org.eclipse.daanse.sql.dialect.api.generator.KnownFunction.PAD_RIGHT,
arguments);
}
return new StringBuilder("SUBSTR(").append(arguments.get(0)).append(" || '").append(" ".repeat(length))
.append("', 1, ").append(length).append(")");
}

/** A sum keeps the type of its operand: an INTEGER column sums to an INTEGER and overflows past 2^31 - 1. */
@Override
public boolean sumKeepsTheTypeOfItsOperand() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ class DerbyDialectTest {
/** Derby casts a number to text only through CHAR. */
@Test
void aNumberAsTextGoesThroughChar() {
assertEquals("TRIM(CAST(x AS CHAR(254)))", new DerbyDialect().generateKnownFunction(
assertEquals("TRIM(CHAR(x))", new DerbyDialect().generateKnownFunction(
org.eclipse.daanse.sql.dialect.api.generator.KnownFunction.TO_TEXT, java.util.List.of("x")).toString());
}

/** Derby has no RPAD: the text and spaces, cut to the length. */
@Test
void aTextIsPaddedWithSpacesAndCut() {
assertEquals("SUBSTR(x || ' ', 1, 4)", new DerbyDialect().generateKnownFunction(
org.eclipse.daanse.sql.dialect.api.generator.KnownFunction.PAD_RIGHT, java.util.List.of("x", "4"))
.toString());
}

/** Derby has no VARBINARY: a binary of a length is VARCHAR FOR BIT DATA, one without a length or beyond it a BLOB. */
@Test
void aBinaryColumnIsBitDataOrABlob() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Stefan Bischof (bipolis.org) - initial
*/
package org.eclipse.daanse.sql.dialect.db.derby;

import static org.assertj.core.api.Assertions.assertThat;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.daanse.sql.dialect.api.generator.KnownFunction;
import org.junit.jupiter.api.Test;

/**
* A number of every type reads as text on Derby, a DOUBLE and a REAL among them, which Derby casts to no text; whole
* numbers make a date.
*/
class DerbyNumberAsTextTest {

@Test
void everyNumberReadsAsText() throws Exception {
DerbyDialect dialect = new DerbyDialect();
try (Connection connection = DriverManager.getConnection("jdbc:derby:memory:numberastext;create=true");
Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE t (d DOUBLE, r REAL, n DECIMAL(10, 2), i INTEGER)");
statement.execute("INSERT INTO t VALUES (2.24, 1.05, 2.50, 100), (17, -3.5, -3, -7)");
List<String> texts = new ArrayList<>();
for (String column : List.of("d", "r", "n", "i")) {
String text = dialect.generateKnownFunction(KnownFunction.TO_TEXT, List.of(column)).toString();
try (ResultSet rows = statement.executeQuery("SELECT " + text + " FROM t")) {
while (rows.next()) {
texts.add(rows.getString(1));
}
}
}
assertThat(texts).containsExactly("2.24", "17.0", "1.05", "-3.5", "2.50", "-3.00", "100", "-7");
}
}

@Test
void aDateOfWholeNumbersReadsAsADate() throws Exception {
DerbyDialect dialect = new DerbyDialect();
try (Connection connection = DriverManager.getConnection("jdbc:derby:memory:datefromparts;create=true");
Statement statement = connection.createStatement()) {
statement.execute("CREATE TABLE t (y SMALLINT, m SMALLINT)");
statement.execute("INSERT INTO t VALUES (2013, 7), (2014, 12)");
String date = dialect.generateKnownFunction(KnownFunction.DATE_FROM_PARTS, List.of("y", "m", "1"))
.toString();
List<String> dates = new ArrayList<>();
try (ResultSet rows = statement.executeQuery("SELECT " + date + " FROM t ORDER BY y")) {
while (rows.next()) {
dates.add(rows.getDate(1).toString());
}
}
assertThat(dates).containsExactly("2013-07-01", "2014-12-01");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,49 @@ public String bigintTypeName() {
return "DECIMAL(15,0)";
}

/** Oracle's longest {@code VARCHAR2} under the standard string size, in bytes. */
private static final int MAX_VARCHAR2 = 4_000;

/**
* A text column of n characters is {@code VARCHAR2 (n CHAR)}, beyond 4000 a {@code CLOB}; a fixed one
* {@code CHAR (n CHAR)}.
*
* <p>
* Oracle counts the length of a {@code VARCHAR2} in bytes unless the column says otherwise, and a character of
* a text in UTF-8 takes up to four: a Thai text of 400 characters needs 536 bytes and more ({@code ORA-12899
* value too large for column}).
*/
@Override
public String nativeType(ColumnMetaData meta) {
java.sql.JDBCType type = meta.dataType();
if (type == null) {
return super.nativeType(meta);
}
return switch (type) {
case VARCHAR, NVARCHAR, LONGVARCHAR, LONGNVARCHAR -> {
int length = meta.columnSize().orElse(255);
yield length > MAX_VARCHAR2 ? "CLOB" : "VARCHAR2(" + length + " CHAR)";
}
case CHAR, NCHAR -> "CHAR(" + meta.columnSize().orElse(1) + " CHAR)";
default -> super.nativeType(meta);
};
}

/** Oracle's longest {@code RAW} under the standard string size. */
private static final int MAX_RAW = 2_000;

/**
* {@code RAW (n)} up to 2000 bytes, a {@code BLOB} without a length or beyond it.
*
* <p>
* Oracle has no VARBINARY: {@code ORA-00902 invalid datatype}.
*/
@Override
public String binaryTypeName(java.util.OptionalInt size) {
return size.isPresent() && size.getAsInt() > 0 && size.getAsInt() <= MAX_RAW ? "RAW(" + size.getAsInt() + ")"
: "BLOB";
}

private volatile org.eclipse.daanse.sql.dialect.api.generator.MergeGenerator cachedMergeGenerator;
private volatile org.eclipse.daanse.sql.dialect.api.generator.ReturningGenerator cachedReturningGenerator;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,31 @@ public void setUp() throws Exception {
dialect = new OracleDialect(org.eclipse.daanse.sql.dialect.api.DialectInitData.fromConnection(connection));
}

/** Oracle has no VARBINARY: a binary of a length is RAW, one without a length or beyond 2000 bytes a BLOB. */
@Test
void aBinaryColumnIsRawOrABlob() {
assertEquals("RAW(100)", dialect.binaryTypeName(java.util.OptionalInt.of(100)));
assertEquals("BLOB", dialect.binaryTypeName(java.util.OptionalInt.empty()));
assertEquals("BLOB", dialect.binaryTypeName(java.util.OptionalInt.of(100_000)));
}

/** Oracle counts a VARCHAR2 in bytes by default: a text column says it counts characters. */
@Test
void aTextColumnCountsCharacters() {
assertEquals("VARCHAR2(400 CHAR)", dialect.nativeType(text(java.sql.JDBCType.NVARCHAR, 400)));
assertEquals("VARCHAR2(255 CHAR)", dialect.nativeType(text(java.sql.JDBCType.VARCHAR, -1)));
assertEquals("CLOB", dialect.nativeType(text(java.sql.JDBCType.VARCHAR, 8_000)));
assertEquals("CHAR(3 CHAR)", dialect.nativeType(text(java.sql.JDBCType.CHAR, 3)));
}

private static org.eclipse.daanse.sql.model.schema.ColumnMetaData text(java.sql.JDBCType type, int size) {
org.eclipse.daanse.sql.model.schema.ColumnMetaData meta = org.mockito.Mockito
.mock(org.eclipse.daanse.sql.model.schema.ColumnMetaData.class);
when(meta.dataType()).thenReturn(type);
when(meta.columnSize()).thenReturn(size < 0 ? java.util.OptionalInt.empty() : java.util.OptionalInt.of(size));
return meta;
}

/** Oracle reads ANSI joins; comma joins were the inherited default, not an Oracle limit. */
@Test
void anInnerJoinIsWrittenWithJoinOn() {
Expand Down
Loading