This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 3b3dc8fdf4 Let a database limit rows before the query as well as after 
it, fixes #8013 (#8032)
3b3dc8fdf4 is described below

commit 3b3dc8fdf40babae47ddfe4e644dbd1269847edc
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Aug 21 09:28:17 2026 +0200

    Let a database limit rows before the query as well as after it, fixes #8013 
(#8032)
---
 .../org/apache/hop/core/database/Database.java     |  8 +-
 .../org/apache/hop/core/database/DatabaseMeta.java |  4 +
 .../org/apache/hop/core/database/IDatabase.java    | 16 ++++
 .../hop/core/database/FirstRowsLimitTest.java      | 98 ++++++++++++++++++++++
 .../ROOT/pages/database/creating-a-dialect.adoc    |  8 +-
 .../hop/databases/access/AccessDatabaseMeta.java   |  7 ++
 .../databases/access/AccessLimitClauseTest.java    | 36 ++++++++
 .../hop/databases/as400/AS400DatabaseMeta.java     |  7 ++
 .../hop/databases/as400/AS400LimitClauseTest.java  | 36 ++++++++
 .../hop/databases/cache/CacheDatabaseMeta.java     |  6 ++
 .../hop/databases/cache/CacheLimitClauseTest.java  | 36 ++++++++
 .../apache/hop/databases/db2/DB2DatabaseMeta.java  |  6 ++
 .../hop/databases/db2/DB2LimitClauseTest.java      | 36 ++++++++
 .../hop/databases/derby/DerbyDatabaseMeta.java     |  7 ++
 .../hop/databases/derby/DerbyLimitClauseTest.java  | 36 ++++++++
 .../hop/databases/duckdb/DuckDBDatabaseMeta.java   |  6 ++
 .../databases/duckdb/DuckDBLimitClauseTest.java    | 36 ++++++++
 .../databases/firebird/FirebirdDatabaseMeta.java   |  6 ++
 .../firebird/FirebirdLimitClauseTest.java          | 36 ++++++++
 .../apache/hop/databases/h2/H2DatabaseMeta.java    |  7 ++
 .../apache/hop/databases/h2/H2LimitClauseTest.java | 36 ++++++++
 .../hypersonic/HypersonicDatabaseMeta.java         |  7 ++
 .../hypersonic/HypersonicLimitClauseTest.java      | 36 ++++++++
 .../hop/databases/impala/ImpalaDatabaseMeta.java   |  7 ++
 .../databases/impala/ImpalaLimitClauseTest.java    | 36 ++++++++
 .../databases/informix/InformixDatabaseMeta.java   |  6 ++
 .../informix/InformixLimitClauseTest.java          | 36 ++++++++
 .../databases/interbase/InterbaseDatabaseMeta.java |  6 ++
 .../interbase/InterbaseLimitClauseTest.java        | 36 ++++++++
 .../hop/databases/iris/IrisDatabaseMeta.java       |  6 ++
 .../hop/databases/iris/IrisLimitClauseTest.java    | 36 ++++++++
 .../hop/databases/monetdb/MonetDBDatabaseMeta.java |  6 ++
 .../databases/monetdb/MonetDBLimitClauseTest.java  | 36 ++++++++
 .../databases/mssql/MsSqlServerDatabaseMeta.java   | 10 +++
 .../mssql/MsSqlServerLimitClauseTest.java          | 36 ++++++++
 .../hop/databases/sqlite/SqliteDatabaseMeta.java   |  6 ++
 .../databases/sqlite/SqliteLimitClauseTest.java    | 36 ++++++++
 .../hop/databases/sybase/SybaseDatabaseMeta.java   |  7 ++
 .../databases/sybase/SybaseLimitClauseTest.java    | 36 ++++++++
 .../databases/sybaseiq/SybaseIQDatabaseMeta.java   | 14 +---
 .../sybaseiq/SybaseIQLimitClauseTest.java          | 36 ++++++++
 .../databases/teradata/TeradataDatabaseMeta.java   |  6 ++
 .../teradata/TeradataLimitClauseTest.java          | 36 ++++++++
 43 files changed, 936 insertions(+), 15 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/database/Database.java 
b/core/src/main/java/org/apache/hop/core/database/Database.java
index 3b9ed239b3..0993104ef5 100644
--- a/core/src/main/java/org/apache/hop/core/database/Database.java
+++ b/core/src/main/java/org/apache/hop/core/database/Database.java
@@ -3716,9 +3716,11 @@ public class Database implements IVariables, 
ILoggingObject, AutoCloseable {
   public List<Object[]> getFirstRows(String tableName, int limit, 
IProgressMonitor monitor)
       throws HopDatabaseException {
     // How a database limits rows is its own syntax, so it is asked rather 
than recognised here.
-    // The row count is capped while reading in any case, so a dialect with no 
limit clause simply
-    // fetches a little more than it needs.
-    String sql = "SELECT * FROM " + tableName;
+    // Some put the clause directly after SELECT and others at the end of the 
statement, so both
+    // are asked for. The row count is capped while reading in any case, so a 
dialect offering
+    // neither simply fetches a little more than it needs.
+    String prefix = limit > 0 ? databaseMeta.getLimitClausePrefix(limit) : "";
+    String sql = "SELECT" + prefix + " * FROM " + tableName;
 
     if (limit > 0) {
       sql += databaseMeta.getLimitClause(limit);
diff --git a/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java 
b/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
index 9ecf163eda..12c23dc364 100644
--- a/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
+++ b/core/src/main/java/org/apache/hop/core/database/DatabaseMeta.java
@@ -1177,6 +1177,10 @@ public class DatabaseMeta extends HopMetadataBase 
implements Cloneable, IHopMeta
     return iDatabase.getLimitClause(nrRows);
   }
 
+  public String getLimitClausePrefix(int nrRows) {
+    return iDatabase.getLimitClausePrefix(nrRows);
+  }
+
   /**
    * @param tableName The table or schema-table combination. We expect this to 
be quoted properly
    *     already!
diff --git a/core/src/main/java/org/apache/hop/core/database/IDatabase.java 
b/core/src/main/java/org/apache/hop/core/database/IDatabase.java
index ecca2f56ff..845ea47507 100644
--- a/core/src/main/java/org/apache/hop/core/database/IDatabase.java
+++ b/core/src/main/java/org/apache/hop/core/database/IDatabase.java
@@ -239,6 +239,22 @@ public interface IDatabase extends Cloneable {
    */
   String getLimitClause(int nrRows);
 
+  /**
+   * The clause that limits the number of rows when the database puts it 
directly after SELECT,
+   * rather than at the end of the statement.
+   *
+   * <p>SQL Server and Sybase IQ write {@code SELECT TOP 10 * FROM t}, where 
PostgreSQL writes
+   * {@code SELECT * FROM t LIMIT 10}. A database uses one form or the other, 
so a dialect overrides
+   * this or {@link #getLimitClause(int)}, not both.
+   *
+   * @param nrRows the number of rows to limit the result to
+   * @return the clause to place after SELECT, with a leading space, or an 
empty string when this
+   *     database limits rows at the end of the statement instead
+   */
+  default String getLimitClausePrefix(int nrRows) {
+    return "";
+  }
+
   /**
    * Returns the minimal SQL to launch in order to determine the layout of the 
resultset for a given
    * database table
diff --git 
a/core/src/test/java/org/apache/hop/core/database/FirstRowsLimitTest.java 
b/core/src/test/java/org/apache/hop/core/database/FirstRowsLimitTest.java
new file mode 100644
index 0000000000..a99c89b4d4
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/database/FirstRowsLimitTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.core.database;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * getFirstRows has to work for databases that limit rows after SELECT as well 
as those that limit
+ * at the end of the statement. See issue 8013.
+ *
+ * <p>The statement is captured on its way to getRows, so this exercises the 
real method rather than
+ * a copy of what it is believed to build.
+ */
+class FirstRowsLimitTest {
+
+  @BeforeAll
+  static void setUpClass() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  private String firstRowsSql(String prefix, String suffix, int limit) throws 
Exception {
+    IDatabase dialect = mock(IDatabase.class);
+    when(dialect.getLimitClausePrefix(limit)).thenReturn(prefix);
+    when(dialect.getLimitClause(limit)).thenReturn(suffix);
+    DatabaseMeta databaseMeta = new DatabaseMeta();
+    databaseMeta.setIDatabase(dialect);
+
+    ILoggingObject log = mock(ILoggingObject.class);
+    when(log.getLogLevel()).thenReturn(LogLevel.NOTHING);
+
+    Database database = spy(new Database(log, new Variables(), databaseMeta));
+    doReturn(List.of()).when(database).getRows(anyString(), anyInt(), any());
+
+    database.getFirstRows("CUSTOMER", limit, null);
+
+    ArgumentCaptor<String> sql = ArgumentCaptor.forClass(String.class);
+    verify(database).getRows(sql.capture(), eq(limit), any());
+    return sql.getValue();
+  }
+
+  @Test
+  void aDatabaseThatLimitsAfterSelectGetsItsClauseThere() throws Exception {
+    assertEquals("SELECT TOP 10 * FROM CUSTOMER", firstRowsSql(" TOP 10", "", 
10));
+  }
+
+  @Test
+  void aDatabaseThatLimitsAtTheEndIsUnaffected() throws Exception {
+    assertEquals("SELECT * FROM CUSTOMER LIMIT 10", firstRowsSql("", " LIMIT 
10", 10));
+  }
+
+  @Test
+  void aDatabaseWithNoLimitClauseAtAllStillProducesValidSql() throws Exception 
{
+    assertEquals("SELECT * FROM CUSTOMER", firstRowsSql("", "", 10));
+  }
+
+  @Test
+  void noLimitMeansNeitherClauseIsAskedFor() throws Exception {
+    assertEquals("SELECT * FROM CUSTOMER", firstRowsSql(" TOP 10", " LIMIT 
10", 0));
+  }
+
+  @Test
+  void theDefaultForADialectThatDeclaresNothingIsEmpty() {
+    assertEquals("", new NoneDatabaseMeta().getLimitClausePrefix(10));
+  }
+}
diff --git 
a/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc 
b/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc
index 3a8c4b1223..be55bf25c8 100644
--- a/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc
+++ b/docs/hop-dev-manual/modules/ROOT/pages/database/creating-a-dialect.adoc
@@ -95,9 +95,15 @@ They are ordinary methods with sensible defaults, and the 
ones that matter most
 |Set this false for databases that invalidate a statement, or the whole 
connection, when a statement fails. SQLite is the example in the tree.
 
 |`getLimitClause(int)`
-|The clause appended *after* the FROM clause to limit rows, such as `" LIMIT 
10"`. It is a suffix; databases that limit rows with a prefix such as `TOP` 
cannot express it here.
+|The clause appended *after* the FROM clause to limit rows, such as `" LIMIT 
10"`.
+
+|`getLimitClausePrefix(int)`
+|The clause placed directly after `SELECT` instead, such as `" TOP 10"`. 
Databases use one form or the other, so override this or `getLimitClause`, not 
both.
 |===
 
+Both include their own leading space, and both default to returning nothing.
+A dialect that offers neither still works: the row count is capped while 
reading, so it simply fetches a little more than it needs.
+
 Prefer overriding a capability over overriding behaviour.
 A capability is a fact about the database that Hop can act on in several 
places; overridden behaviour only fixes the one place you overrode.
 
diff --git 
a/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
 
b/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
index aa6110aa86..f851c67344 100644
--- 
a/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
+++ 
b/plugins/databases/access/src/main/java/org/apache/hop/databases/access/AccessDatabaseMeta.java
@@ -37,6 +37,13 @@ import org.apache.hop.core.row.IValueMeta;
     classLoaderGroup = "access-db")
 @GuiPlugin(id = "GUI-MSAccessDatabaseMeta")
 public class AccessDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** Access limits rows with TOP, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessLimitClauseTest.java
 
b/plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessLimitClauseTest.java
new file mode 100644
index 0000000000..02cc016ba0
--- /dev/null
+++ 
b/plugins/databases/access/src/test/java/org/apache/hop/databases/access/AccessLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.access;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class AccessLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 5", new AccessDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new AccessDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
 
b/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
index 40bb6ae1a9..fbbf13b040 100644
--- 
a/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
+++ 
b/plugins/databases/as400/src/main/java/org/apache/hop/databases/as400/AS400DatabaseMeta.java
@@ -36,6 +36,13 @@ import org.apache.hop.core.row.IValueMeta;
     classLoaderGroup = "as400-db")
 @GuiPlugin(id = "GUI-AS400DatabaseMeta")
 public class AS400DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** DB2 for i limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " FETCH FIRST " + nrRows + " ROWS ONLY";
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400LimitClauseTest.java
 
b/plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400LimitClauseTest.java
new file mode 100644
index 0000000000..b0fc9815f5
--- /dev/null
+++ 
b/plugins/databases/as400/src/test/java/org/apache/hop/databases/as400/AS400LimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.as400;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class AS400LimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" FETCH FIRST 5 ROWS ONLY", new 
AS400DatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new AS400DatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
 
b/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
index f0f4824662..936a707aa0 100644
--- 
a/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
+++ 
b/plugins/databases/cache/src/main/java/org/apache/hop/databases/cache/CacheDatabaseMeta.java
@@ -36,6 +36,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-CacheDatabaseMeta")
 public class CacheDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
 
+  /** Cache limits rows with TOP, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   public static final String CONST_ALTER_TABLE = "ALTER TABLE ";
 
   @Override
diff --git 
a/plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheLimitClauseTest.java
 
b/plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheLimitClauseTest.java
new file mode 100644
index 0000000000..0e3b19337e
--- /dev/null
+++ 
b/plugins/databases/cache/src/test/java/org/apache/hop/databases/cache/CacheLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.cache;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class CacheLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 5", new CacheDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new CacheDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
 
b/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
index 9436a8ccf9..650866139a 100644
--- 
a/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
+++ 
b/plugins/databases/db2/src/main/java/org/apache/hop/databases/db2/DB2DatabaseMeta.java
@@ -37,6 +37,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-DB2DatabaseMeta")
 public class DB2DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
 
+  /** DB2 limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " FETCH FIRST " + nrRows + " ROWS ONLY";
+  }
+
   private static final String ALTER_TABLE = "ALTER TABLE ";
 
   @Override
diff --git 
a/plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2LimitClauseTest.java
 
b/plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2LimitClauseTest.java
new file mode 100644
index 0000000000..0bbb2aa02f
--- /dev/null
+++ 
b/plugins/databases/db2/src/test/java/org/apache/hop/databases/db2/DB2LimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.db2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class DB2LimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" FETCH FIRST 5 ROWS ONLY", new 
DB2DatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new DB2DatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
 
b/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
index 60476c7ae8..1fb2410ca2 100644
--- 
a/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
+++ 
b/plugins/databases/derby/src/main/java/org/apache/hop/databases/derby/DerbyDatabaseMeta.java
@@ -36,6 +36,13 @@ import org.apache.hop.core.util.Utils;
     classLoaderGroup = "derby-db")
 @GuiPlugin(id = "GUI-DerbyDatabaseMeta")
 public class DerbyDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** Derby limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " FETCH FIRST " + nrRows + " ROWS ONLY";
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyLimitClauseTest.java
 
b/plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyLimitClauseTest.java
new file mode 100644
index 0000000000..884b1199be
--- /dev/null
+++ 
b/plugins/databases/derby/src/test/java/org/apache/hop/databases/derby/DerbyLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.derby;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class DerbyLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" FETCH FIRST 5 ROWS ONLY", new 
DerbyDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new DerbyDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
 
b/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
index 039b817e5d..00794c691c 100644
--- 
a/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
+++ 
b/plugins/databases/duckdb/src/main/java/org/apache/hop/databases/duckdb/DuckDBDatabaseMeta.java
@@ -41,6 +41,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-DuckDBDatabaseMeta")
 public class DuckDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
 
+  /** DuckDB limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   private static final List<IDatabaseTypeRule> TYPE_RULES =
       DatabaseTypes.rules()
           // As of DuckDB JDBC 0.10.0 the Calendar overloads of setDate and 
setTimestamp are not
diff --git 
a/plugins/databases/duckdb/src/test/java/org/apache/hop/databases/duckdb/DuckDBLimitClauseTest.java
 
b/plugins/databases/duckdb/src/test/java/org/apache/hop/databases/duckdb/DuckDBLimitClauseTest.java
new file mode 100644
index 0000000000..eb0015f2b9
--- /dev/null
+++ 
b/plugins/databases/duckdb/src/test/java/org/apache/hop/databases/duckdb/DuckDBLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.duckdb;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class DuckDBLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new DuckDBDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new DuckDBDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/firebird/src/main/java/org/apache/hop/databases/firebird/FirebirdDatabaseMeta.java
 
b/plugins/databases/firebird/src/main/java/org/apache/hop/databases/firebird/FirebirdDatabaseMeta.java
index 3b34b1c8bd..813304f52f 100644
--- 
a/plugins/databases/firebird/src/main/java/org/apache/hop/databases/firebird/FirebirdDatabaseMeta.java
+++ 
b/plugins/databases/firebird/src/main/java/org/apache/hop/databases/firebird/FirebirdDatabaseMeta.java
@@ -38,6 +38,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-FirebirdDatabaseMeta")
 public class FirebirdDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /** Firebird limits rows with FIRST, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " FIRST " + nrRows;
+  }
+
   public static final String CONST_TIMESTAMP = "TIMESTAMP";
   public static final String CONST_SMALLINT = "SMALLINT";
   public static final String CONST_INTEGER = "INTEGER";
diff --git 
a/plugins/databases/firebird/src/test/java/org/apache/hop/databases/firebird/FirebirdLimitClauseTest.java
 
b/plugins/databases/firebird/src/test/java/org/apache/hop/databases/firebird/FirebirdLimitClauseTest.java
new file mode 100644
index 0000000000..cd89a34949
--- /dev/null
+++ 
b/plugins/databases/firebird/src/test/java/org/apache/hop/databases/firebird/FirebirdLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.firebird;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class FirebirdLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" FIRST 5", new 
FirebirdDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new FirebirdDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/h2/src/main/java/org/apache/hop/databases/h2/H2DatabaseMeta.java
 
b/plugins/databases/h2/src/main/java/org/apache/hop/databases/h2/H2DatabaseMeta.java
index 7534d52d4e..e012d7bf23 100644
--- 
a/plugins/databases/h2/src/main/java/org/apache/hop/databases/h2/H2DatabaseMeta.java
+++ 
b/plugins/databases/h2/src/main/java/org/apache/hop/databases/h2/H2DatabaseMeta.java
@@ -36,6 +36,13 @@ import org.apache.hop.core.util.Utils;
     classLoaderGroup = "h2-db")
 @GuiPlugin(id = "GUI-H2DatabaseMeta")
 public class H2DatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** H2 limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/h2/src/test/java/org/apache/hop/databases/h2/H2LimitClauseTest.java
 
b/plugins/databases/h2/src/test/java/org/apache/hop/databases/h2/H2LimitClauseTest.java
new file mode 100644
index 0000000000..307886dc49
--- /dev/null
+++ 
b/plugins/databases/h2/src/test/java/org/apache/hop/databases/h2/H2LimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.h2;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class H2LimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new H2DatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new H2DatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/hypersonic/src/main/java/org/apache/hop/databases/hypersonic/HypersonicDatabaseMeta.java
 
b/plugins/databases/hypersonic/src/main/java/org/apache/hop/databases/hypersonic/HypersonicDatabaseMeta.java
index cea2328802..b8cf813709 100644
--- 
a/plugins/databases/hypersonic/src/main/java/org/apache/hop/databases/hypersonic/HypersonicDatabaseMeta.java
+++ 
b/plugins/databases/hypersonic/src/main/java/org/apache/hop/databases/hypersonic/HypersonicDatabaseMeta.java
@@ -36,6 +36,13 @@ import org.apache.hop.core.util.Utils;
     classLoaderGroup = "hypersonic-db")
 @GuiPlugin(id = "GUI-HypersonicDatabaseMeta")
 public class HypersonicDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
+
+  /** HSQLDB limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/hypersonic/src/test/java/org/apache/hop/databases/hypersonic/HypersonicLimitClauseTest.java
 
b/plugins/databases/hypersonic/src/test/java/org/apache/hop/databases/hypersonic/HypersonicLimitClauseTest.java
new file mode 100644
index 0000000000..c4bdf5e9fa
--- /dev/null
+++ 
b/plugins/databases/hypersonic/src/test/java/org/apache/hop/databases/hypersonic/HypersonicLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.hypersonic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class HypersonicLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new HypersonicDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new HypersonicDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/impala/src/main/java/org/apache/hop/databases/impala/ImpalaDatabaseMeta.java
 
b/plugins/databases/impala/src/main/java/org/apache/hop/databases/impala/ImpalaDatabaseMeta.java
index d43a4fd8cb..70ed4bf463 100644
--- 
a/plugins/databases/impala/src/main/java/org/apache/hop/databases/impala/ImpalaDatabaseMeta.java
+++ 
b/plugins/databases/impala/src/main/java/org/apache/hop/databases/impala/ImpalaDatabaseMeta.java
@@ -39,6 +39,13 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
     classLoaderGroup = "impala-db")
 @GuiPlugin(id = "GUI-ClouderaImpalaDatabaseMeta")
 public class ImpalaDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** Impala limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   public static final String CONST_ALTER_TABLE = "ALTER TABLE ";
   private static final int VARCHAR_LIMIT = 65_535;
 
diff --git 
a/plugins/databases/impala/src/test/java/org/apache/hop/databases/impala/ImpalaLimitClauseTest.java
 
b/plugins/databases/impala/src/test/java/org/apache/hop/databases/impala/ImpalaLimitClauseTest.java
new file mode 100644
index 0000000000..19b3b818fd
--- /dev/null
+++ 
b/plugins/databases/impala/src/test/java/org/apache/hop/databases/impala/ImpalaLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.impala;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class ImpalaLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new ImpalaDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new ImpalaDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/informix/src/main/java/org/apache/hop/databases/informix/InformixDatabaseMeta.java
 
b/plugins/databases/informix/src/main/java/org/apache/hop/databases/informix/InformixDatabaseMeta.java
index e2c32c7369..26b5f92152 100644
--- 
a/plugins/databases/informix/src/main/java/org/apache/hop/databases/informix/InformixDatabaseMeta.java
+++ 
b/plugins/databases/informix/src/main/java/org/apache/hop/databases/informix/InformixDatabaseMeta.java
@@ -38,6 +38,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-InformixDatabaseMeta")
 public class InformixDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /** Informix limits rows with FIRST, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " FIRST " + nrRows;
+  }
+
   @GuiWidgetElement(
       id = "servername",
       order = "10",
diff --git 
a/plugins/databases/informix/src/test/java/org/apache/hop/databases/informix/InformixLimitClauseTest.java
 
b/plugins/databases/informix/src/test/java/org/apache/hop/databases/informix/InformixLimitClauseTest.java
new file mode 100644
index 0000000000..7a9825cccb
--- /dev/null
+++ 
b/plugins/databases/informix/src/test/java/org/apache/hop/databases/informix/InformixLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.informix;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class InformixLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" FIRST 5", new 
InformixDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new InformixDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/interbase/src/main/java/org/apache/hop/databases/interbase/InterbaseDatabaseMeta.java
 
b/plugins/databases/interbase/src/main/java/org/apache/hop/databases/interbase/InterbaseDatabaseMeta.java
index 715597838f..c98129f507 100644
--- 
a/plugins/databases/interbase/src/main/java/org/apache/hop/databases/interbase/InterbaseDatabaseMeta.java
+++ 
b/plugins/databases/interbase/src/main/java/org/apache/hop/databases/interbase/InterbaseDatabaseMeta.java
@@ -36,6 +36,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-InterbaseDatabaseMeta")
 public class InterbaseDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /** Interbase limits rows with FIRST, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " FIRST " + nrRows;
+  }
+
   public static final String CONST_SMALLINT = "SMALLINT";
   public static final String CONST_INTEGER = "INTEGER";
   public static final String CONST_VARCHAR = "VARCHAR";
diff --git 
a/plugins/databases/interbase/src/test/java/org/apache/hop/databases/interbase/InterbaseLimitClauseTest.java
 
b/plugins/databases/interbase/src/test/java/org/apache/hop/databases/interbase/InterbaseLimitClauseTest.java
new file mode 100644
index 0000000000..7675882547
--- /dev/null
+++ 
b/plugins/databases/interbase/src/test/java/org/apache/hop/databases/interbase/InterbaseLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.interbase;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class InterbaseLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" FIRST 5", new 
InterbaseDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new InterbaseDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/iris/src/main/java/org/apache/hop/databases/iris/IrisDatabaseMeta.java
 
b/plugins/databases/iris/src/main/java/org/apache/hop/databases/iris/IrisDatabaseMeta.java
index c6976a9e5a..1c20f3882b 100644
--- 
a/plugins/databases/iris/src/main/java/org/apache/hop/databases/iris/IrisDatabaseMeta.java
+++ 
b/plugins/databases/iris/src/main/java/org/apache/hop/databases/iris/IrisDatabaseMeta.java
@@ -37,6 +37,12 @@ import org.apache.hop.core.row.IValueMeta;
 @GuiPlugin(id = "GUI-IrisDatabaseMeta")
 public class IrisDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
 
+  /** IRIS limits rows with TOP, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   public static final String CONST_ALTER_TABLE = "ALTER TABLE ";
 
   @Override
diff --git 
a/plugins/databases/iris/src/test/java/org/apache/hop/databases/iris/IrisLimitClauseTest.java
 
b/plugins/databases/iris/src/test/java/org/apache/hop/databases/iris/IrisLimitClauseTest.java
new file mode 100644
index 0000000000..7b1eec0e2d
--- /dev/null
+++ 
b/plugins/databases/iris/src/test/java/org/apache/hop/databases/iris/IrisLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.iris;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class IrisLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 5", new IrisDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new IrisDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/monetdb/src/main/java/org/apache/hop/databases/monetdb/MonetDBDatabaseMeta.java
 
b/plugins/databases/monetdb/src/main/java/org/apache/hop/databases/monetdb/MonetDBDatabaseMeta.java
index 14262c7a02..85cb97a872 100644
--- 
a/plugins/databases/monetdb/src/main/java/org/apache/hop/databases/monetdb/MonetDBDatabaseMeta.java
+++ 
b/plugins/databases/monetdb/src/main/java/org/apache/hop/databases/monetdb/MonetDBDatabaseMeta.java
@@ -37,6 +37,12 @@ import org.apache.hop.core.util.Utils;
 @GuiPlugin(id = "GUI-MonetDBDatabaseMeta")
 public class MonetDBDatabaseMeta extends BaseDatabaseMeta implements IDatabase 
{
 
+  /** MonetDB limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   public static final String CONST_BIGINT = "BIGINT";
   public static final String CONST_DOUBLE = "DOUBLE";
   public static ThreadLocal<Boolean> safeModeLocal = new ThreadLocal<>();
diff --git 
a/plugins/databases/monetdb/src/test/java/org/apache/hop/databases/monetdb/MonetDBLimitClauseTest.java
 
b/plugins/databases/monetdb/src/test/java/org/apache/hop/databases/monetdb/MonetDBLimitClauseTest.java
new file mode 100644
index 0000000000..0e6f97bde6
--- /dev/null
+++ 
b/plugins/databases/monetdb/src/test/java/org/apache/hop/databases/monetdb/MonetDBLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.monetdb;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class MonetDBLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new MonetDBDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new MonetDBDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
 
b/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
index 18217215ec..25c9ef4361 100644
--- 
a/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
+++ 
b/plugins/databases/mssql/src/main/java/org/apache/hop/databases/mssql/MsSqlServerDatabaseMeta.java
@@ -44,6 +44,16 @@ import org.apache.hop.metadata.api.HopMetadataProperty;
 @GuiPlugin(id = "GUI-MSSQLServerDatabaseMeta")
 public class MsSqlServerDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /**
+   * SQL Server limits rows with TOP, between SELECT and the column list.
+   *
+   * <p>Inherited by the native dialect, which shares this syntax.
+   */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   public static final String CONST_ALTER_TABLE = "ALTER TABLE ";
 
   @GuiWidgetElement(
diff --git 
a/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerLimitClauseTest.java
 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerLimitClauseTest.java
new file mode 100644
index 0000000000..2aa6655095
--- /dev/null
+++ 
b/plugins/databases/mssql/src/test/java/org/apache/hop/databases/mssql/MsSqlServerLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.mssql;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT rather than at the end of the 
statement. */
+class MsSqlServerLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 25", new 
MsSqlServerDatabaseMeta().getLimitClausePrefix(25));
+  }
+
+  @Test
+  void thereIsNoClauseAtTheEndOfTheStatement() {
+    assertEquals("", new MsSqlServerDatabaseMeta().getLimitClause(25));
+  }
+}
diff --git 
a/plugins/databases/sqlite/src/main/java/org/apache/hop/databases/sqlite/SqliteDatabaseMeta.java
 
b/plugins/databases/sqlite/src/main/java/org/apache/hop/databases/sqlite/SqliteDatabaseMeta.java
index 24b229708a..631c425d8e 100644
--- 
a/plugins/databases/sqlite/src/main/java/org/apache/hop/databases/sqlite/SqliteDatabaseMeta.java
+++ 
b/plugins/databases/sqlite/src/main/java/org/apache/hop/databases/sqlite/SqliteDatabaseMeta.java
@@ -47,6 +47,12 @@ import org.apache.hop.core.row.value.ValueMetaFactory;
 @GuiPlugin(id = "GUI-SQLiteDatabaseMeta")
 public class SqliteDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
 
+  /** SQLite limits rows at the end of the statement. */
+  @Override
+  public String getLimitClause(int nrRows) {
+    return " LIMIT " + nrRows;
+  }
+
   private static final List<IDatabaseTypeRule> TYPE_RULES =
       DatabaseTypes.rules()
           // Dynamic typing means a binary column is as likely to hold text.
diff --git 
a/plugins/databases/sqlite/src/test/java/org/apache/hop/databases/sqlite/SqliteLimitClauseTest.java
 
b/plugins/databases/sqlite/src/test/java/org/apache/hop/databases/sqlite/SqliteLimitClauseTest.java
new file mode 100644
index 0000000000..74af4cf490
--- /dev/null
+++ 
b/plugins/databases/sqlite/src/test/java/org/apache/hop/databases/sqlite/SqliteLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.sqlite;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows at the end of the statement. See issue 8013. */
+class SqliteLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAtTheEnd() {
+    assertEquals(" LIMIT 5", new SqliteDatabaseMeta().getLimitClause(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new SqliteDatabaseMeta().getLimitClausePrefix(5));
+  }
+}
diff --git 
a/plugins/databases/sybase/src/main/java/org/apache/hop/databases/sybase/SybaseDatabaseMeta.java
 
b/plugins/databases/sybase/src/main/java/org/apache/hop/databases/sybase/SybaseDatabaseMeta.java
index e61ec769de..beceadf2e8 100644
--- 
a/plugins/databases/sybase/src/main/java/org/apache/hop/databases/sybase/SybaseDatabaseMeta.java
+++ 
b/plugins/databases/sybase/src/main/java/org/apache/hop/databases/sybase/SybaseDatabaseMeta.java
@@ -35,6 +35,13 @@ import org.apache.hop.core.row.IValueMeta;
     classLoaderGroup = "sybase-db")
 @GuiPlugin(id = "GUI-SybaseDatabaseMeta")
 public class SybaseDatabaseMeta extends BaseDatabaseMeta implements IDatabase {
+
+  /** Sybase ASE limits rows with TOP, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   @Override
   public int[] getAccessTypeList() {
     return new int[] {DatabaseMeta.TYPE_ACCESS_NATIVE};
diff --git 
a/plugins/databases/sybase/src/test/java/org/apache/hop/databases/sybase/SybaseLimitClauseTest.java
 
b/plugins/databases/sybase/src/test/java/org/apache/hop/databases/sybase/SybaseLimitClauseTest.java
new file mode 100644
index 0000000000..88722fc962
--- /dev/null
+++ 
b/plugins/databases/sybase/src/test/java/org/apache/hop/databases/sybase/SybaseLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.sybase;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class SybaseLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 5", new SybaseDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new SybaseDatabaseMeta().getLimitClause(5));
+  }
+}
diff --git 
a/plugins/databases/sybaseiq/src/main/java/org/apache/hop/databases/sybaseiq/SybaseIQDatabaseMeta.java
 
b/plugins/databases/sybaseiq/src/main/java/org/apache/hop/databases/sybaseiq/SybaseIQDatabaseMeta.java
index 572e47df26..ed540d0657 100644
--- 
a/plugins/databases/sybaseiq/src/main/java/org/apache/hop/databases/sybaseiq/SybaseIQDatabaseMeta.java
+++ 
b/plugins/databases/sybaseiq/src/main/java/org/apache/hop/databases/sybaseiq/SybaseIQDatabaseMeta.java
@@ -45,18 +45,10 @@ public class SybaseIQDatabaseMeta extends BaseDatabaseMeta 
implements IDatabase
     return true;
   }
 
-  /**
-   * Sybase IQ limits rows with TOP, which goes between SELECT and the column 
list rather than at
-   * the end of the statement. {@link IDatabase#getLimitClause} is appended 
after the FROM clause,
-   * so TOP cannot be expressed through it; returning it here would produce 
{@code SELECT * FROM t
-   * TOP 10}, which does not parse.
-   *
-   * <p>Callers cap the row count while reading, so the effect of having no 
clause here is that a
-   * few more rows cross the wire, not that too many are returned.
-   */
+  /** Sybase IQ limits rows with TOP, between SELECT and the column list. */
   @Override
-  public String getLimitClause(int nrRows) {
-    return "";
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
   }
 
   @Override
diff --git 
a/plugins/databases/sybaseiq/src/test/java/org/apache/hop/databases/sybaseiq/SybaseIQLimitClauseTest.java
 
b/plugins/databases/sybaseiq/src/test/java/org/apache/hop/databases/sybaseiq/SybaseIQLimitClauseTest.java
new file mode 100644
index 0000000000..ddd153e9a1
--- /dev/null
+++ 
b/plugins/databases/sybaseiq/src/test/java/org/apache/hop/databases/sybaseiq/SybaseIQLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.sybaseiq;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT rather than at the end of the 
statement. */
+class SybaseIQLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 25", new 
SybaseIQDatabaseMeta().getLimitClausePrefix(25));
+  }
+
+  @Test
+  void thereIsNoClauseAtTheEndOfTheStatement() {
+    assertEquals("", new SybaseIQDatabaseMeta().getLimitClause(25));
+  }
+}
diff --git 
a/plugins/databases/teradata/src/main/java/org/apache/hop/databases/teradata/TeradataDatabaseMeta.java
 
b/plugins/databases/teradata/src/main/java/org/apache/hop/databases/teradata/TeradataDatabaseMeta.java
index afeea9afac..bd7782fe65 100644
--- 
a/plugins/databases/teradata/src/main/java/org/apache/hop/databases/teradata/TeradataDatabaseMeta.java
+++ 
b/plugins/databases/teradata/src/main/java/org/apache/hop/databases/teradata/TeradataDatabaseMeta.java
@@ -44,6 +44,12 @@ import org.apache.hop.core.util.Utils;
 @GuiPlugin(id = "GUI-TeradataDatabaseMeta")
 public class TeradataDatabaseMeta extends BaseDatabaseMeta implements 
IDatabase {
 
+  /** Teradata limits rows with TOP, between SELECT and the column list. */
+  @Override
+  public String getLimitClausePrefix(int nrRows) {
+    return " TOP " + nrRows;
+  }
+
   private static final List<IDatabaseTypeRule> TYPE_RULES =
       DatabaseTypes.rules()
           // Hop marks "a date, not a timestamp" with a precision of one.
diff --git 
a/plugins/databases/teradata/src/test/java/org/apache/hop/databases/teradata/TeradataLimitClauseTest.java
 
b/plugins/databases/teradata/src/test/java/org/apache/hop/databases/teradata/TeradataLimitClauseTest.java
new file mode 100644
index 0000000000..1a4a7894d9
--- /dev/null
+++ 
b/plugins/databases/teradata/src/test/java/org/apache/hop/databases/teradata/TeradataLimitClauseTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.databases.teradata;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/** This database limits rows after SELECT. See issue 8013. */
+class TeradataLimitClauseTest {
+
+  @Test
+  void rowsAreLimitedAfterSelect() {
+    assertEquals(" TOP 5", new TeradataDatabaseMeta().getLimitClausePrefix(5));
+  }
+
+  @Test
+  void theOtherFormIsNotUsed() {
+    assertEquals("", new TeradataDatabaseMeta().getLimitClause(5));
+  }
+}

Reply via email to