Repository: dbeaver/dbeaver
Issue: Oracle Alternative Quoting Mechanism Not Properly Ended

#### System information: 
- Windows 10 64-bit
- DBeaver 21.2.3
- No additional extensions

#### Connection specification:
- Oracle Database 12c
- Oracle JDBC driver 12.2.0.1
- No tunnels or proxies

#### Describe the problem you're observing:

Alternative quoting mechanism ("Q") for string literals is not working correctly when the alternative quoting character is repeated at the end of a string.

#### Steps to reproduce:

I execute this SQL statement:

`SELECT q'!Test!!' FROM DUAL;`

And get this error:

> SQL Error [933] [42000]: ORA-00933: SQL command not properly ended

If I first highlight the whole SQL statement or remove the semi-colon before executing it, it runs without errors and outputs:

```
Q'!TEST!!'|
----------+
Test!     |
```

In both cases, the appearance is wrong with all of `' FROM DUAL;` being SQL string colour and `q'{{Test}}` not being so.

Other scenarios:

* If you add an odd number of quotes to the string (e.g. `q'!It's a test!!'`), it'll run fine but still appear wrong.

* If the string has the alternative quoting character followed by a quote within it (e.g. `q'!Test!'!'`), it'll correctly throw an error.

* If the character before the final alternative quoting character is anything else (e.g. `q'!Test! !'`), it'll both run and look fine.

* If the string has an odd number of the alternative quoting characters (e.g. `q'!Test!!!'`), it'll both run and look fine.

#### Include any warning/errors/backtraces from the logs
```
!ENTRY org.jkiss.dbeaver.model 4 0 2021-10-19 17:20:57.514
!MESSAGE SQL Error [933] [42000]: ORA-00933: SQL command not properly ended

!SUBENTRY 1 org.jkiss.dbeaver.model 4 0 2021-10-19 17:20:57.515
!MESSAGE SQL Error [933] [42000]: ORA-00933: SQL command not properly ended

!SUBENTRY 2 org.jkiss.dbeaver.model 4 0 2021-10-19 17:20:57.515
!MESSAGE Error : 933, Position : 27, Sql = SELECT q'!Test!!' FROM DUAL;, OriginalSql = SELECT q'!Test!!' FROM DUAL;, Error Msg = ORA-00933: SQL command not properly ended

!STACK 0
Error : 933, Position : 27, Sql = SELECT q'!Test!!' FROM DUAL;, OriginalSql = SELECT q'!Test!!' FROM DUAL;, Error Msg = ORA-00933: SQL command not properly ended

	at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:498)
	at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:446)
	at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1054)
	at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:623)
	at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:252)
	at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:612)
	at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:213)
	at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:37)
	at oracle.jdbc.driver.T4CStatement.executeForDescribe(T4CStatement.java:733)
	at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:904)
	at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1082)
	at oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:1737)
	at oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:1692)
	at oracle.jdbc.driver.OracleStatementWrapper.execute(OracleStatementWrapper.java:300)
	at org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCStatementImpl.execute(JDBCStatementImpl.java:330)
	at org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCStatementImpl.executeStatement(JDBCStatementImpl.java:130)
	at org.jkiss.dbeaver.ui.editors.sql.execute.SQLQueryJob.executeStatement(SQLQueryJob.java:513)
	at org.jkiss.dbeaver.ui.editors.sql.execute.SQLQueryJob.lambda$0(SQLQueryJob.java:444)
	at org.jkiss.dbeaver.model.exec.DBExecUtils.tryExecuteRecover(DBExecUtils.java:171)
	at org.jkiss.dbeaver.ui.editors.sql.execute.SQLQueryJob.executeSingleQuery(SQLQueryJob.java:431)
	at org.jkiss.dbeaver.ui.editors.sql.execute.SQLQueryJob.extractData(SQLQueryJob.java:816)
	at org.jkiss.dbeaver.ui.editors.sql.SQLEditor$QueryResultsContainer.readData(SQLEditor.java:3462)
	at org.jkiss.dbeaver.ui.controls.resultset.ResultSetJobDataRead.lambda$0(ResultSetJobDataRead.java:118)
	at org.jkiss.dbeaver.model.exec.DBExecUtils.tryExecuteRecover(DBExecUtils.java:171)
	at org.jkiss.dbeaver.ui.controls.resultset.ResultSetJobDataRead.run(ResultSetJobDataRead.java:116)
	at org.jkiss.dbeaver.ui.controls.resultset.ResultSetViewer$ResultSetDataPumpJob.run(ResultSetViewer.java:4718)
	at org.jkiss.dbeaver.model.runtime.AbstractJob.run(AbstractJob.java:105)
	at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
```

Changed files:
plugins/org.jkiss.dbeaver.ext.oracle/src/org/jkiss/dbeaver/ext/oracle/sql/OracleDialectRules.java

diff --git a/plugins/org.jkiss.dbeaver.ext.oracle/src/org/jkiss/dbeaver/ext/oracle/sql/OracleDialectRules.java b/plugins/org.jkiss.dbeaver.ext.oracle/src/org/jkiss/dbeaver/ext/oracle/sql/OracleDialectRules.java
index 60e3991bb7..6d27c76a92 100644
--- a/plugins/org.jkiss.dbeaver.ext.oracle/src/org/jkiss/dbeaver/ext/oracle/sql/OracleDialectRules.java
+++ b/plugins/org.jkiss.dbeaver.ext.oracle/src/org/jkiss/dbeaver/ext/oracle/sql/OracleDialectRules.java
@@ -61,28 +61,9 @@ class OracleDialectRules implements TPRuleProvider {
                     if (!Character.isLetterOrDigit(quoteStartChar)) {
                         // Probably a Q-string
                         char quoteEndChar = getQuoteEndChar(quoteStartChar);
-                        int charsRead = 0;
-                        boolean isQuote = true;
-                        for (;;) {
-                            c = scanner.read();
-                            charsRead++;
-                            if (c == quoteEndChar) {
-                                c = scanner.read();
-                                charsRead++;
-                                if (c == '\\'') {
-                                    break;
-                                }
-                            } else if (c == TPCharacterScanner.EOF) {
-                                isQuote = false;
-                                break;
-                            }
-                        }
-                        if (isQuote) {
+
+                        if (tryReadQString(scanner, quoteEndChar)) {
                             return stringToken;
-                        } else {
-                            for (int i = 0; i < charsRead; i++) {
-                                scanner.unread();
-                            }
                         }
                     } else {
                         quoteStartChar = (char) -1;
@@ -99,6 +80,26 @@ class OracleDialectRules implements TPRuleProvider {
             return TPTokenAbstract.UNDEFINED;
         }
 
+        private boolean tryReadQString(TPCharacterScanner scanner, char quoteEndChar) {
+            int charsRead = 0;
+            int prevChar = -1, currChar = -1;
+            boolean isEndOfLiteral, isEndOfText;
+            do {
+                prevChar = currChar;
+                currChar = scanner.read();
+                charsRead++;
+                isEndOfLiteral = prevChar == quoteEndChar && currChar == '\\'';
+                isEndOfText = currChar == TPCharacterScanner.EOF;
+            } while (!isEndOfLiteral && !isEndOfText);
+
+            if (isEndOfText) {
+                for (int i = 0; i < charsRead; i++) {
+                    scanner.unread();
+                }
+            }
+            return isEndOfLiteral;
+        }
+
         private static char getQuoteEndChar(char startChar) {
             switch (startChar) {
                 case '<': return '>';