Project

General

Profile

Feature #9456

implement SQL Server dialect and helpers

Added by Eric Faulhaber over 1 year ago. Updated 6 months ago.

Status:
WIP
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
production:
No
env_name:
topics:

Related issues

Related to Database - Bug #10171: Word Index/CONTAINS support for MS SQL Server. Test
Related to Database - Bug #10288: Encrypted connetion to SQL Server New

History

#1 Updated by Ovidiu Maxiniuc over 1 year ago

  • Status changed from New to WIP
  • Assignee set to Ovidiu Maxiniuc

I set up a MSSQL instance for a new customer and put to the test the DDLs FWD generates today. I know for sure a few years ago, the generated DDLs were syntactically correct, therefore these are some regressions most likely due to switch to in-house now ORM / persistence support.

Momentarily, there are two kind of issues:
  • (in table definitions) bad generation of computed columns (type is inferred, and must NOT be specified);
  • (in index definitions) bad generation of index components (leak of legacy names, instead of columns).

I am preparing a branch for fixing these issues and further work on this task.

#3 Updated by Ovidiu Maxiniuc about 1 year ago

A few weeks ago I created task branch 9456a for holding changes which would bring SQL Server dialect closer to all other already proven to work. It contains a few revisions which include changes for:
  • schema generation;
  • basic runtime support;
  • a good percent of SQL server side UDFs. These are a bit time consuming as the language is a bit different than others and some time the result is not as expected (for example when handling null values). It is a known fact that Microsoft as its own way of doing things, especially with their SQL Server. They will be tracked by #9455.

The work was not very structured, I added fixes of the issues as they popped out. I used a local testcase and attempted to get the hotel project running, but I was not yet successful.

#4 Updated by Ovidiu Maxiniuc about 1 year ago

With my latest changes from 9456a, I was able to convert and import the hotel database into a MS SQL Server instance. The data looks fine on my visual inspection. I also updated and committed the build.xml and build_db.xml of the hotel_gui project so now the import into MSSQL (including drop/create database, installation of schema and UDFs) works. Of course, the dialect was added to p2j.cfg.xml.

Maybe the following are more schema-related, but the runtime ans the dialect should expect the information to honour a specific convention. I will continue to add support code in runtime after the we decide how the data is actually stored.

So, here there are some aspects we need to discuss. I cannot take full responsibility for the decisions below:
  • the 1.7K hard limit of the index keys. A warning is emitted for nearly all indexes which contain character data;
  • collations. As with MariaDB, we are forced to pick one from the list of available collations. The good pare is that the list seems to be more extensive. Most likely, the customer will have to make a decision here. Also, see below, (nearly) all collations have ci/cs variants.
  • case sensitivity. By default, the CS of the columns in a table inherit the collation of the table/instance. We implemented previously the support for this as a computed column as upper(trim(<column>)). From my tests, the trim() looks superfluous, the trimming is automatically applied when sorting. The problem is that this is depending on the collation: if the collation is case insensitive, is really not needed. I am thinking whether we should apply the collation in DDL, at the time the schema is converted. The definitions would look like this:
        ci_text varchar(128) COLLATE sql_latin1_general_cp1_ci_as,
        cs_text varchar(128) COLLATE sql_latin1_general_cp1_cs_as,
    The only problem is that the collation should be known at the time of conversion and not when the database is set up. More than that, to change the collation a schema conversion should be performed in FWD (well, there is also the option to do a text replacement in the files). In spite of this I think we may want to go this way, instead of current solution of injecting the __i<column> computed column for index component;
  • nulls last This dialect does not offer such syntax. Therefore we implemented (not currently supported by our FqlToSqlConverter) a workaround by injecting the following expression: case when <column> is null then 1 else 0 end. I was about to get this fixed, but then I realised that the indexes are not defined using this expression. Therefore, they will not be actually used. I experimented with a related idea: what if we define a computed column like this:
        <column>__null as (case when room_type is null then 1 else 0 end)
    This can be added as index component and the SQL to be emitted are not as complex as adding the full case expression. My question here is: while this looks more elegant for columns which are index components, needing to sort on other column will also require this computed column, reaching the point where all columns will have this pair. And if we stick to current case-sensitivity solution (using _i prefix), for the character fields we will end up having 3 columns. For example:
       guest varchar(128),                -- case sensitive value
       __iguest as upper(rtrim(guest)),   -- case insensitive index component
       guest__null as (case when guest is null then 1 else 0 end),-- helper for sorting 'null last'

#5 Updated by Ovidiu Maxiniuc about 1 year ago

To the list above I need to add another bullet:
  • MSSQL does not allow multiple null in unique indexes.
    The solution we used to work around this issue was to force injection of PK field as the last index component. This way, all records become unique so allowing the records which otherwise would break the unique constraint to be stored in database. This is just temporary, NOT the final fix: since all records are seen now as unique, the index is not putting the unique constraint any more.

#6 Updated by Constantin Asofiei about 1 year ago

MSSQL has this syntax - see https://stackoverflow.com/questions/767657/how-do-i-create-a-unique-constraint-that-also-allows-nulls :

CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;

We can try and add non-mandatory fields in an unique index to the WHERE clause, but what happens if there are 3 non-mandatory fields in an unique index, and field 1 is null for 1/3rd of records, 1/3rd second field is null, the other the third field, with no overlap - then then index will not index anything.

#7 Updated by Ovidiu Maxiniuc about 1 year ago

  • % Done changed from 0 to 30

Revision 15960 of 9456a contains the fix from #9456-6. It was successful on hotel import and (probably - technical issues prevented the import to finish correctly) on customer's databases.

#8 Updated by Ovidiu Maxiniuc about 1 year ago

A 'new' old issue: the MSSQL and lack of support for boolean literals. That is the reason the Dialect.supportsBooleanDatatype() was implemented. It generally return true, except for MSSQL dialect. There were fragments of code which handled this, but since the dialect support was not maintained, these were not running correctly.

So the data-type used for storing logical values in MSSQL is bit. However, this is a synonym of int(1) and not compatible with logical operators (and, or, not). There is no true or false literals. When an such a parameter is passed to one of out UDFs, it will be replaced with 0 or 1, respectively. However, when a logical expression requires a literal it must be replaced by a 'native' logical expression. The simplest solution we found was to test against 1 (aka true) in expressions like: (field = 1) and (boolFn(...) = 1), (?subst = 1) or (1 = 1), etc.

I updated the FQL preprocessor so that usages of boolean values are processed accordingly, but this needs also testing and possible future adjustments for specific syntaxes.

A point Constantin highlighted is the occurrences of SQL statements hardcoded or composed in FWD code for internal management. These should be dialect independent, but should written in such a way to avoid the logical literals. The good part is that they are mostly used for temp-tables and metas (which always use H2).

#9 Updated by Ovidiu Maxiniuc about 1 year ago

One more thing, related to paging. We knew that MSSQL does not have a LIMIT / OFFSET syntax for query pagination, instead it uses TOP (like SELECT TOP 5 ... to select only the first 5 rows). Since then, they added a new option for ORDER BY. It looks like this (I simplified a bit):

ORDER BY <order_by_expressions> [ OFFSET <offset> ROWS [ FETCH NEXT <limit> ROWS ONLY ] ] 

What is important:
  • the order of limit and offset is reversed compared to other dialects;
  • if the limit is desired, offset is mandatory (also reversed);
  • both options require the presence of a parent ORDER BY clause.

In 9456a I have implemented most of the support for first two bullets, assuming that the ORDER BY will be added to all queries, anyway. However, last night, while smoke testing the hotel_gui project back-ended by SQL Server database I realised that the unique queries do not inject this clause to it (from a logical perspective, that is not necessary as the result is expected to be... unique, therefore nothing to sort). I am working on solving this issue, most likely the solution is to force the injection of an ORDER BY recid clause for UNIQUE queries, hopefully only for this dialect.

#10 Updated by Ovidiu Maxiniuc about 1 year ago

The ORDER BY issue was partially resolved. In case of UNIQUE finds, the HQL bundle was expressly setting the sorting to null. I enabled it to add sorting on PK for MSSQL. Although I am thinking whether pagination is necessary for these queries. Logically, limit and offset are pointless here. The reason to have a limit 2 is to check whether the result is indeed unique without fetching the full table. So yes, the current solution I implemented is the best.

Good news: now the hotel_gui starts and you can switch to all tabs to see the imported data.

Bad news:
  • I hit another MSSQL implementation restriction, one we were not aware of in the iteration of several years ago: the releaseSavepoint() method in JDBC 3.0 is not supported by Microsoft JDBC Driver for SQL Server ๐Ÿ™
  • and one I could not find in documentation: the FORWARD_ONLY MSSQL queries cannot call ResultSet.isLast() because it is 'not scrollable'.

#11 Updated by Stefanel Pezamosca about 1 year ago

  • Related to Bug #10171: Word Index/CONTAINS support for MS SQL Server. added

#12 Updated by Ovidiu Maxiniuc about 1 year ago

Related to my note #9456-8: with my current changes in 9456a, I encountered in hotel_runtime a query where predicate contains:

        dbo.matches_ssb(upper(reservatio0_.guest), upper(reservatio0_.guest), 1) = 1 or 
            not ((upper(rtrim(reservatio0_.guest)) is not null) = 1) and 1 = 1) and 
Beside the matches_ssb not being implemented yet, there is a problem with comparing the result of is not null to 1. The left-side of the = operator is a native true boolean value, but the right one is not (just a bit/integer). This should have been also replaced with (1 = 1) as with the right side of the and operator. To note that when testing the result of matches_ssb() the predicate correctly tests it with = 1 since the UDF will return a converted logic value (that's a bit).

Of course, a bit of preprocessing can be done here and drop completely and 1 = 1, but the problem will still exist for not ((guest is null) = 1). This should be preprocessed and the ideal result would be as simple as (guest is not null).

#13 Updated by Greg Shah about 1 year ago

  • I hit another MSSQL implementation restriction, one we were not aware of in the iteration of several years ago: the releaseSavepoint() method in JDBC 3.0 is not supported by Microsoft JDBC Driver for SQL Server ๐Ÿ™
  • and one I could not find in documentation: the FORWARD_ONLY MSSQL queries cannot call ResultSet.isLast() because it is 'not scrollable'.

Please update the master list of SQLServer limitations. If there is no master list of SQLServer limitations, please create one.

#14 Updated by Ovidiu Maxiniuc about 1 year ago

Created a table with SQL Server specific issues in Limitations and Workarounds.

#15 Updated by Ovidiu Maxiniuc about 1 year ago

As reported previously, I encountered some issues while fixing the FWD support for logical/boolean expressions in MSSQL dialect.

  • As we know, there is no boolean data type, instead bit (which is an integer type, synonym to int(1)) is used. Because of this, it is NOT compatible with AND, OR, NOT. Therefore if a logical field needs to be checked (find first book where book.isactive), the workaround is to rewrite it as a logical value: select * from book where book.isactive = 1. This is the simple case.
  • A more complicated problem occurs when the result of a logical expression is used as parameter like in: find first book where string(book.isactive and p1) eq "yes". Without specific preprocessing, the SQL query will be
    select * from book where dbo.tostring_b((book.isactive = 1) and ?) = 'yes'
    The problem is that the result is of SQL-internal logical type and cannot be passed to tostring_b(bit). We cannot handle this using cast or convert. The only solution is to rewrite it as:
    select * from book where dbo.tostring_b(case when ((book.isactive = 1) and ?) then 1 else 0 end) = 'yes'
  • There is also a 3rd level issue: the native logical values are not comparable. Let's take the following example: find first book where (book.book-id eq 101) = (book.isbn eq "102") no-error. For other dialects, this would be converted as:
    select * from book where (book.book-id = 101) = (book.isbn = '102')
    Analysing the predicate, there are two boolean expression tested for equality. But SQL Server refuses to compare them (using the other relational operators !=, >, <=, etc will not work as well). For = we could have replace it with xor operator, if that existed. The only solution to this problem is, again, to use inline case so that the query will become:
    select * from book where (case when (book.book-id = 101) then 1 else 0 end) = (case when (book.isbn = '102') then 1 else 0 end)

I created the following table with nodes and their parents when doing the FQL preprocessing:

          \ Parent
Child \          
Logical Operators
(AND, OR, NOT)
Relational Operators
(EQ, NE, LT, GT, LTE, GTE)
UDF LPARENS Root of WHERE
Predicate
Literal
(TRUE / FALSE)
Replace by (1=1) / (1=0) Replace by 1 / 0 Replace by 1 / 0 Go up to the first non LPARENS parent
and apply the rule for that parent
in this table
Replace by (1=1) / (1=0)
(Actually the WHERE node is dropped)
Substitution
(bit / integer)
Replace by (1=1) / (1=0) Replace by 1 / 0 Replace by 1 / 0 Replace by (1=1) / (1=0)
(Actually the WHERE node is dropped)
UDF
(bit / integer)
Replace by (Node=1) - - Replace by (Node=1)
Logical
Operator
- Replace by
CASE WHEN Node THEN 1 ELSE 0 END
Replace by
CASE WHEN Node THEN 1 ELSE 0 END
-
Relational
Operator
- Replace by
CASE WHEN Node THEN 1 ELSE 0 END
Replace by
CASE WHEN Node THEN 1 ELSE 0 END
-
LPARENS - - - -

I think applying all rules according to the above table will generate correct queries for SQL Server. The big problem is the resulting query complexity which directly (may) affects performance.

#16 Updated by Constantin Asofiei about 1 year ago

  • reviewer Eric Faulhaber added

Ovidiu/Stefanel/Eduard: please lets get 9456a stabilized and ready for review tomorrow (this will include all the UDF, word-index and other SQL-server specific dialect changes). Maybe we can have this merged this week, so I can rebase 9457c again. Eric - please review when this task is in 'review' status.

This will allow us easier testing of the 9457c branch, and further fixes will be in a separate 9456b branch. Thank you.

#17 Updated by Ovidiu Maxiniuc about 1 year ago

  • reviewer deleted (Eric Faulhaber)

9456a seems stable now after the fixes for word tables.

The customer's 'small' database imports without any issue.

The big one is another kind of animal.
I attempted to run today at least 6 imports and none finished with success. But this is not because of FWD but because of the Windows2025 VM. It rebooted 3 times (Example: The computer has rebooted from a bugcheck. The bugcheck was: 0x000000ef (0xffffae8573b5f340, 0x0000000000000000, 0xffffae8573b5f340, 0x0000000000000000). A dump was saved in: C:\WINDOWS\MEMORY.DMP. Report Id: b4456417-be0b-4679-a75f-e78bd048cd8f.). The MEMORY.DMP is binary and overwritten. And I have these report Ids, but no means to find them, there are no find tool in event viewer.
Beside that, for some reasons, Windows networking keeps renewing its IP. I do not know why, it has a reserved IP in DHCP router. All of a sudden it jumps to 169.254.98.125 instead of the local 192.168.0.130. Doing so while the import is running causes the process to halt since FWD is not able to locate the database server.

To work around the issue I tried first to throttle the network bandwidth. I leaned then that this is only possible for outgoing data from VM and not for the data sent to it by FWD. A second attempt was to throttle the disk speed. I limited it to 100MB/s and the import seemed to be working, although at 1/4 of speed. However, although the CPU was low (10%) with a 4 import threads matching the 4 cores of VM the disk busy-ness was high: 90+% all the time, even if the actual data written to disk was much lower, rarely reaching the limit imposed by VM. It crashed, in the end. :(

At the end of the day, the imports fails with:

     [java] [c3p0] A PooledConnection that has already signalled a Connection error is still in use!
     [java] [c3p0] Another error has occurred [ com.microsoft.sqlserver.jdbc.SQLServerException: The connection is closed. ] which will not be reported to listeners!
...
     [java] com.microsoft.sqlserver.jdbc.SQLServerException: Connection timed out
     [java] Caused by: java.net.SocketException: Connection timed out
     [java] xlnkxlglpost.d: Error processing import data; 550200 of 1015366 record(s) successfully processed; 0 record(s) uncommitted due to this error;  465166 record(s) dropped. Cause: Error rolling back database transaction
or similar errors, if the VM does not completely crash (BSOD) and reboot.

#18 Updated by Ovidiu Maxiniuc about 1 year ago

  • reviewer Eric Faulhaber added

#19 Updated by Ovidiu Maxiniuc about 1 year ago

  • % Done changed from 30 to 80

As discussed during technical meet from Monday I tried executing the import from within the guest OS. That required moving the whole project, including data inside Windows VM. The small database imported OK, but the large database only reached 42% before BSoD happened.

That was just an attempt to work around the IP reassigning and other time-outs caused by inter-OS communication. But now I am facing a more serious issue. The moment these issues occurred can be associated with switching from varchar to nvarchar. But this does not make much sense: it is not SQL Server crashing but the OS. I am thinking that these repeated imports (heavy data movement) might have broken something within the VM. I am planning to install SQL Server on a bare metal machine. It is slower, but it should not suffer from same issues as a VM.

#20 Updated by Constantin Asofiei about 1 year ago

Ovidiu, can 9456a be moved to review?

About the data/dump/ database folder missing: if you haven't already fixed it, or you think it takes more time, please postpone it. I'll change the scripts to automatically create the folder when the app is setup.

#21 Updated by Ovidiu Maxiniuc about 1 year ago

  • reviewer Constantin Asofiei added

Branch 9456a was rebased to trunk/16032. Pushed up to revision 16060.

It is ready for review.

Note: I have some pending changes but they will are related to import rather than conversion (mostly reporting error messages). They are not extensive and can be reviewed later, in isolation.

#22 Updated by Ovidiu Maxiniuc about 1 year ago

  • Status changed from WIP to Review

#23 Updated by Constantin Asofiei about 1 year ago

Ovidiu, first results on conversion: nulls last is added on indexes for H2 dialect (this includes the meta schema and I assume all temp-table DDL). Why this change?

#24 Updated by Ovidiu Maxiniuc about 1 year ago

This is not intentional. I wonder why I did not notice it. It is a regression. Thank you for reporting it. I'm fixing it ASAP.

#25 Updated by Ovidiu Maxiniuc about 1 year ago

Constantin Asofiei wrote:

Ovidiu, first results on conversion: nulls last is added on indexes for H2 dialect (this includes the meta schema and I assume all temp-table DDL). Why this change?

This still puzzles me. My question is why the option was not added previously. In Dialect.getCreateIndexString() we call orderByNulls() for each component and the implementation of orderByNulls() in P2JH2Dialect it calls the super method then, unconditionally, sb.append(asc ? " nulls last" : " nulls first");. This hasn`t changed.
Yet, the trunk code seems to ignore this line.

I can easily drop the overriding orderByNulls() method from P2JH2Dialect, but I still need a reason for why it was not executed in trunk.

#26 Updated by Constantin Asofiei about 1 year ago

This is in trunk.
  • Dialect has this:
       public int orderByNulls(StringBuilder sb,
                               String expression,
                               String field,
                               boolean asc,
                               boolean forceAsc,
                               boolean mandatory)
    
    • this is called from Dialect.getCreateIndexString
    • P2JH2Dialect overrides only this:
         public int orderByNulls(StringBuilder sb, String expression, String field, boolean asc, boolean forceAsc)
         {
      

So you either changed the overrides in P2JH2Dialect or now you are calling the orderByNulls which is overriden in P2JH2Dialect.

#27 Updated by Constantin Asofiei about 1 year ago

  • Related to Bug #10288: Encrypted connetion to SQL Server added

#28 Updated by Eduard Soltan about 1 year ago

Ovidiu, looking at the udfs tests I found a couple of problems:

1) When creating the udfs aliases, we get with reflection the functions from Functions.java and we add the suffix based on the method parameters.
However there are cases when the method has as parameter a primitive data type (int, long, etc), but this cases aren't handled by SQLServerHelper.getSqlDecoration or SQLServerHelper.getSqlType

=== modified file 'src/com/goldencode/p2j/persist/dialect/SQLServerHelper.java'
--- old/src/com/goldencode/p2j/persist/dialect/SQLServerHelper.java    2025-05-31 00:17:02 +0000
+++ new/src/com/goldencode/p2j/persist/dialect/SQLServerHelper.java    2025-07-15 14:51:18 +0000
@@ -199,9 +199,11 @@
             return "bit";

          case "java.lang.Integer":
+         case "int":
             return "int";

          case "java.lang.Long":
+         case "long":
             return "bigint";

          case "java.lang.String":
@@ -238,9 +240,11 @@
             return 'b';

          case "java.lang.Integer":
+         case "int":
             return 'i';

          case "java.lang.Long":
+         case "long":
             return 'l';

          case "java.lang.String":

#29 Updated by Ovidiu Maxiniuc about 1 year ago

Thank you for noticing this issue.

The SQLServerHelper class has more issues than this. It creates a list of DDLs for a set of CLR/IKVM function aliases which we co not support any more. I have some pending changes to resolve that.

There's a second problem here. The parameters of Functions (and Operators) are usually values from columns of H2 database. This will work correctly only those columns are NOT NULL. Since this assumption does not always hold, the parameters of the UDFs should be nullable (wrappers). The Functions must be updated to reflect that or we will face NPEs when that happens (H2 only). Due to this, I've chosen to keep your changes in getSqlDecoration() (getSqlType() is no longer present).

Is there a second one?

#30 Updated by Ovidiu Maxiniuc about 1 year ago

Branch 9456a was rebase to trunk r16048. Current revision is 16078.

#31 Updated by Constantin Asofiei about 1 year ago

Ovidiu, is src/com/goldencode/p2j/persist/dialect/P2JSQLServer2008Dialect.java.THIS.THIS supposed to exist?

#32 Updated by Ovidiu Maxiniuc about 1 year ago

No. bzr added it without my knowledge. I never use add during rebase. I think it was confused because the file was updated in trunk, but it was removed in the branch.
Removed in r16079.

#33 Updated by Ovidiu Maxiniuc about 1 year ago

  • % Done changed from 80 to 90

I added missing javadoc and reverted blank changes.
The support for boolean data type should be working correctly now.

Additionally, 9456a was rebased. Current revision is 16088.

PS: The DDLs 9456a generates for H2 dialect are correct. There is a regression in trunk which passed unnoticed.

#34 Updated by Constantin Asofiei about 1 year ago

  • Status changed from Review to Internal Test

Ovidiu, please consider 9456a frozen for testing.

#35 Updated by Constantin Asofiei about 1 year ago

Eduard, please double-check your pending UDF changes for SqlServer and after that commit them to 9456a. We will merge 9456a after that.

#36 Updated by Eduard Soltan about 1 year ago

Committed on 9456a, rev. 16089. Added some udfs functions overloads, and small functions fix ups. Only sqlserver/udfs.sql changes.

#37 Updated by Constantin Asofiei about 1 year ago

  • Status changed from Internal Test to Merge Pending

Ovidiu, please merge 9456a after 10114b.

#38 Updated by Ovidiu Maxiniuc about 1 year ago

I peeked at r16089. There are some updates which do not seem logical (although they will work with the default CI collation):

-          WHEN LOWER(@interval_unit) IN (N'year', N'years') THEN DATEADD(YEAR, @interval_amount, @datetime)
+          WHEN LOWER(@interval_unit) IN (N'YEAR', N'YEARS') THEN DATEADD(YEAR, @interval_amount, @datetime)
-          WHEN LOWER(@interval_unit) IN (N'month', N'months') THEN DATEADD(MONTH, @interval_amount, @datetime)
+          WHEN LOWER(@interval_unit) IN (N'MONTH', N'MONTHS') THEN DATEADD(MONTH, @interval_amount, @datetime)
[...]
We will address that is subsequent branch.

#39 Updated by Ovidiu Maxiniuc about 1 year ago

Branch 9456a was merged into trunk as 16062 then it was archived.

#40 Updated by Ovidiu Maxiniuc about 1 year ago

I have created 9456b from trunk 16062 for subsequent changes.

#41 Updated by Ovidiu Maxiniuc about 1 year ago

  • Status changed from Merge Pending to WIP

#42 Updated by Teodor Gorghe 12 months ago

Ovidiu, the following statement does not work after 9456a changes (with tstcasesdb database):
- CURRENT-VALUE (customsequence10) = 10

Test error:

โ•ท
โ”œโ”€ JUnit Jupiter โœ”
โ”œโ”€ JUnit Vintage โœ”
โ”œโ”€ JUnit Platform Suite โœ”
โ””โ”€ FWD Test โœ”
   โ””โ”€ tests.TestCurrentValueSequenceSet โœ”
      โ””โ”€ testCurrentValueSequence โœ˜ [00000001:00000005:bogus-->local/tstcasesdb/primary] Error executing SQL statement:  select setval(customsequence10, 10)

ant importdb error:

     [exec] CREATE SEQUENCE
     [exec] DROP SEQUENCE
     [exec] CREATE SEQUENCE
     [exec] DROP SEQUENCE
     [exec] CREATE SEQUENCE
     [exec] DROP SEQUENCE
     [exec] CREATE SEQUENCE
     [exec] DROP SEQUENCE
     [exec] CREATE SEQUENCE
     [exec] psql:ddl/schema_table_tstcasesdb_postgresql.sql:237: NOTICE:  sequence "customsequence10" does not exist, skipping
     [exec] psql:ddl/schema_table_tstcasesdb_postgresql.sql:239: NOTICE:  sequence "customsequence100" does not exist, skipping
     [exec] psql:ddl/schema_table_tstcasesdb_postgresql.sql:241: NOTICE:  sequence "customsequencecycle" does not exist, skipping
     [exec] psql:ddl/schema_table_tstcasesdb_postgresql.sql:243: NOTICE:  sequence "maxcustomerseq" does not exist, skipping

This happens both on SQL Server and Postgresql.

#43 Updated by Ovidiu Maxiniuc 12 months ago

I have fixed syntax for set sequence value in PostgreSQL dialect in 9457c/16098. I think we need to get this patch into trunk, as well.
The SQL Server dialect does not seem to be affected, in my tests.

#44 Updated by Eduard Soltan 12 months ago

Committed on 9456b, rev. 16071.

Revision 16071 contains changes from 10283a, this fixes the coalesce, and fix for the problem from #10283-18.

#45 Updated by Constantin Asofiei 12 months ago

Eduard - there are usages of 'lower' in udf/sqlserver/udfs.sql where it matches with an uppercase text, see:

          WHEN LOWER(@interval_unit) IN (N'YEAR', N'YEARS') THEN DATEADD(YEAR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MONTH', N'MONTHS') THEN DATEADD(MONTH, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'WEEK', N'WEEKS') THEN DATEADD(WEEK, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'DAY', N'DAYS') THEN DATEADD(DAY, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'HOUR', N'HOURS') THEN DATEADD(HOUR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MINUTE', N'MINUTES') THEN DATEADD(MINUTE, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'SECOND', N'SECONDS') THEN DATEADD(SECOND, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MILLISECOND', N'MILLISECONDS') THEN DATEADD(MILLISECOND, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'YEAR', N'YEARS') THEN DATEADD(YEAR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MONTH', N'MONTHS') THEN DATEADD(MONTH, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'WEEK', N'WEEKS') THEN DATEADD(WEEK, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'DAY', N'DAYS') THEN DATEADD(DAY, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'HOUR', N'HOURS') THEN DATEADD(HOUR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MINUTE', N'MINUTES') THEN DATEADD(MINUTE, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'SECOND', N'SECONDS') THEN DATEADD(SECOND, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MILLISECOND', N'MILLISECONDS') THEN DATEADD(MILLISECOND, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'YEAR', N'YEARS') THEN DATEADD(YEAR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MONTH', N'MONTHS') THEN DATEADD(MONTH, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'WEEK', N'WEEKS') THEN DATEADD(WEEK, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'DAY', N'DAYS') THEN DATEADD(DAY, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'HOUR', N'HOURS') THEN DATEADD(HOUR, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MINUTE', N'MINUTES') THEN DATEADD(MINUTE, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'SECOND', N'SECONDS') THEN DATEADD(SECOND, @interval_amount, @datetime)
          WHEN LOWER(@interval_unit) IN (N'MILLISECOND', N'MILLISECONDS') THEN DATEADD(MILLISECOND, @interval_amount, @datetime)

from grep -i "lower" udf/sqlserver/udfs.sql

Please check the same for 'upper' (to not match with an lowercase text)

Please fix this in 9456b these cases.

All function names look to be lowercase, except this:

grep "CREATE FUNCTION" udf/sqlserver/udfs.sql | cut -d' ' -f 3 | cut -d'(' -f 1  | grep -E "[A-Z]" 
dbo.getFWDVersion_

#46 Updated by Eduard Soltan 12 months ago

Committed on 9456b, rev. 16076.

Convert booleans for dirty database in case primary database does not support boolean type. Refs. #10283-28. Issue related to dirty database fql bundle issue.

For #9456-45, I remember there was a weird case in testcases/udfs/4gl testcases. Looking into it.

#47 Updated by Eduard Soltan 12 months ago

Committed on 9456b, rev. 16077. AddInterval and GetInterval function fix-ups

#48 Updated by Ovidiu Maxiniuc 12 months ago

Eduard Soltan wrote:

Committed on 9456b, rev. 16076.
Convert booleans for dirty database in case primary database does not support boolean type. Refs. #10283-28. Issue related to dirty database fql bundle issue.

Eduard,
I saw you tried to fix the issues related to dirty database.

I am not sure that is the correct approach. In all cases the dialect and its convertBoolean parameters are passed along stack-trace except for one case: in FQLHelper:434: the FQLPreprocessor.get() is invoked with dirtyDialect, but convertBoolean from primaryDialect. This is most likely incorrect: if the primary is MSSQL, this will cause the booleans to be converted for the dirty database which is always H2, causing invalid statements to be generated.

I understand that there is an issue with the number of parameters #10283-28 (DIRTY DATABASE BUNDLE ISSUE), but IMO, this is not the right solution.

#49 Updated by Eduard Soltan 12 months ago

Ovidiu Maxiniuc wrote:

This is most likely incorrect: if the primary is MSSQL, this will cause the booleans to be converted for the dirty database which is always H2, causing invalid statements to be generated.

I thought that the generated FQLs by convertBoolean are syntactically different, but identical from a semantic point of view.

And saw that majority of statements generated by convertBoolean are (1 = 0, 1 = 1, case ? then 1 else 0, etc) which are also valid in other dialects.

What is the statement which would not be valid in other dialects?

#50 Updated by Ovidiu Maxiniuc 12 months ago

Yes, that is only a particular case of transformation. Also, many of the transformations, although more complicated (therefore possible having negative effects on performance) are still syntactically correct for other dialects (H2 in this case).

But that is not always the case. There are cases when an UDF (in H2 that is a method from Functions.java) is expecting a boolean value and convertBoolean will transform the logical expression into a numeric (BIT is actually identical to int(1)). In these cases the execution will fail. If we are lucky, H2 will use an implicit conversion from int to boolean. I did not give it a try, but taking into consideration H2 is Java and Java is strong-typed, chances are not good.

#51 Updated by Eduard Soltan 12 months ago

Ovidiu Maxiniuc wrote:

But that is not always the case. There are cases when an UDF (in H2 that is a method from Functions.java) is expecting a boolean value and convertBoolean will transform the logical expression into a numeric (BIT is actually identical to int(1)). In these cases the execution will fail. If we are lucky, H2 will use an implicit conversion from int to boolean. I did not give it a try, but taking into consideration H2 is Java and Java is strong-typed, chances are not good.

Understood, I found something in FqlHelper.postProcessBundle,

// Store query substitution parameter indices.
// TODO:  currently, these are shared, and not dialect specific, but only because we do not inline
//        parameters from this class.  Were this to change, we would need to maintain separate
//        [ParameterIndices] objects for the primary and dirty database statements.

I think I should take the following approach, and maintain separate ParameterIndices for the primary and dirty database statements.

#52 Updated by Eduard Soltan 12 months ago

Committed on 9456b, rev. 16081.

Reverted boolean conversion for dirty database, separated parameters for FQL query into primary and dirty database.

#53 Updated by Constantin Asofiei 12 months ago

Ovidiu, 9456b/16097 looks good (I've committed some formatting fixes).

Please commit the dialect NPE fix from #7143-1637, after that we will start testing.

#54 Updated by Constantin Asofiei 12 months ago

  • reviewer Alexandru Lungu added

Alexandru, please review the p2j.persist changes.

#55 Updated by Ovidiu Maxiniuc 12 months ago

  • % Done changed from 90 to 100
  • reviewer deleted (Alexandru Lungu)

I did the best effort to detect the dialect in Session in order to avoid the NPE regression reported in #7143-1634 and next.

There was another regression related to PK acquiring which was mandatory before the branch would reach the trunk.

The 9456b branch is officially frozen now (at revision 16098). Further work will be committed to newly created 9992a branch.

#56 Updated by Ovidiu Maxiniuc 12 months ago

  • reviewer Alexandru Lungu added

#57 Updated by Constantin Asofiei 12 months ago

Ovidiu, some notes:
  • the "FirstNumber".equals(firstColumnLabel) approach (appears twice now in the code) I think needs to be in the dialect.
  • the releaseSavepoint part - for non-local dbs, that should also check via JDBC for the dialect.

#58 Updated by Constantin Asofiei 12 months ago

Constantin Asofiei wrote:

  • the releaseSavepoint part - for non-local dbs, that should also check via JDBC for the dialect.

In other words: if the dialect can't be resolved, get it from JDBC, regardless if local or non-local

#59 Updated by Ovidiu Maxiniuc 12 months ago

OK, I will remove the outer if.

#60 Updated by Alexandru Lungu 12 months ago

Review of 9456b:
  • I see plenty of isLimitBeforeOffset checks that alter the behavior of persistence code. Sometimes it is not clear (at least to me) how the relative position of LIMIT vs OFFSET actually related to the conditional.
    • isOrderByRequired returns true if !isLimitBeforeOffset(). Reading this code alone doesn't make much sense to me. The default it to use ORDER BY, but if the query has at most 1 record, then this should return 1 (this is documented in the javadoc). I can't tell how isLimitBeforeOffset relates to this flow.
    • FQLHelper generated a different clause in whereClause (that apparently seems null safe) depending on isLimitBeforeOffset. This is again a bit confusing. I can't tell why toWhereExpendeNullExpression would be favored vs toWhereExpression if isLimitBeforeOffset holds.
    • others
    • I personally can infer an intent of differentiating SQLServer from other dialects when dealing with SQL composition, but I think some other (more suggestive) API should be used instead of isLimitBeforeOffset. Something like forceOrderBy or forceExpandedNullExpressionInWhere (though I prefer a shorter name:).
  • I like the idea of moving code out of RAQ.executeImpl in smaller methods (i.e. populateParameters). Hooray!
  • typo in toWhereExpendeNullExpression?

Is toWhereExpendeNullExpression a AND / OR based null check vs toWhereExpression that is a COALESCE based null check? In other words, will SQL Server use AND / OR to make where clauses null safe, while other dialects will still use COALESCE? This is how I interpret the code. If that is the case, note that toWhereExpendeNullExpression is the implementation in FWD before #6667 that eventually changed that in COALESCE based SQL. See #8914-5.

If yes, then are all these changes regarding parameter indices and bundles done to accommodate COALESCE for H2 and AND/OR SQL for SQLServer? In this case, I really think this is a huge over-complication with little to no gain.

My conclusion: If COALESCE is something that is not supported by SQL Server, then I really think we should rather drop it entirely and unify the SQL generation across all dialects. It doesn't make sense to have COALESCE for PG, MariaDB and H2, but AND/OR clauses for SQLServer. Personally, I even think that (as mentioned in #8914-22):

Performance-wise: doing "null-check augmentation" is a performance constraint we need to live with to be able to replicate the 4GL behavior 100%. However, I am inclined to think that the planning engine is more tolerant with OR instead of COALESCE. PostgreSQL is able to do OR merge part of the planner, especially as there is a small number of clauses. Your example in #8914-12 os a bit opaque, because I don't know what fields are actually indexed in your example. I would go ahead with a slight more investigation on this.

I added Tomasz as I watcher. Tomasz, can you review 9456b (SortCriterion.toWhereExpendeNUllExpression)? Can we rather use such AND/OR approach for all dialects and eventually replace the OG SortCriterion.toWhereExpression with this implementation entirely? The over-complication of having different parameter indices and different bundles for different dialects (especially for dirty H2 vs persistent SQLServer) seems unreasonable.

#61 Updated by Eduard Soltan 12 months ago

Alexandru Lungu wrote:

Is toWhereExpendeNullExpression a AND / OR based null check vs toWhereExpression that is a COALESCE based null check? In other words, will SQL Server use AND / OR to make where clauses null safe, while other dialects will still use COALESCE?

That is right.

If yes, then are all these changes regarding parameter indices and bundles done to accommodate COALESCE for H2 and AND/OR SQL for SQLServer? In this case, I really think this is a huge over-complication with little to no gain.

Not only for that, there are cases when user defined where clause might be different in SqlServer dialect and H2 bundles.

def var ch1 as char.
ch1 = ''.

find first book2 where ch1 = ''.

Active bundle generated is:

PRIMARY STATEMENTS
   select book2.recid from Book2__Impl__ as book2 where 1 = 1 order by book2.recid asc
DIRTY STATEMENTS
   select book2.recid from Book2__Impl__ as book2 where ?0 order by book2.recid asc
GETTERS

Because we can't have such sql statements in SqlServer select * from table where true, whilst in H2 or other dialects we can.

In fact the COALESCE OR EXTENDED NULL CHECK part that is generated by fwd will be exactly the same for both dialects in the bundle (COALESCE if primary database is pg, mariadb, etc and EXTENDED NULL CHECK if primary database is SQLServer).

#62 Updated by Eduard Soltan 12 months ago

Alexandru Lungu wrote:

Review of 9456b:
  • I see plenty of isLimitBeforeOffset checks that alter the behavior of persistence code. Sometimes it is not clear (at least to me) how the relative position of LIMIT vs OFFSET actually related to the conditional.
  • isOrderByRequired returns true if !isLimitBeforeOffset(). Reading this code alone doesn't make much sense to me. The default it to use ORDER BY, but if the query has at most 1 record, then this should return 1 (this is documented in the javadoc). I can't tell how isLimitBeforeOffset relates to this flow.

To use the following syntax offset ? row fetch next ? rows only in a sql query in SQLServer, we must have an order by defined before that otherwise we get a syntax error.

FQLHelper generated a different clause in whereClause (that apparently seems null safe) depending on isLimitBeforeOffset. This is again a bit confusing. I can't tell why toWhereExpendeNullExpression would be favored vs toWhereExpression if isLimitBeforeOffset holds.

Oops! Here the intent was to use !primaryDialect.supportsBooleanDatatype(), but from behaviour point of view they are the same thing since the only class that overrides supportsBooleanDatatype and isLimitBeforeOffset is P2JSQLServer2012Dialect.

#63 Updated by Alexandru Lungu 12 months ago

Not only for that, there are cases when user defined where clause might be different in SqlServer dialect and H2 bundles.

Hmm, I understand. There was a TODO as well (that can be removed in #9456).

      // TODO:  currently, these are shared, and not dialect specific, but only because we do not inline
      //        parameters from this class.  Were this to change, we would need to maintain separate
      //        [ParameterIndices] objects for the primary and dirty database statements.

So we should definitely have different indexing of parameters for dirty vs primary statements anyway - and this is way more important for SQLServer.

I am OK then with the changes to RAQ to distinguish between dirty and primary parameters. But this still doesn't justify the need of two different implementations (with and without COALESCE). It just motivates the need of different parameters for the base WHERE. In order words, can't we just always use your toWhereExpendeNUllExpression for good? IMHO, it won't hurt H2, MariaDB or PostgreSQL. But this will require a bit more testing and performance analysis.

To use the following syntax offset ? row fetch next ? rows only in a sql query in SQLServer, we must have an order by defined before that otherwise we get a syntax error.

isLimitBeforeOffset doesn't imply that the ORDER BY is mandatory. Other (unsupported) dialects might allow limit before offset without enforcing order by. From my POV, these two principles are distinct. Please use a isOrderByMandatory or something like that instead - with the risk of bloating the dialect, I favor clarity :)

#64 Updated by Eduard Soltan 12 months ago

Alexandru Lungu wrote:

I am OK then with the changes to RAQ to distinguish between dirty and primary parameters. But this still doesn't justify the need of two different implementations (with and without COALESCE). It just motivates the need of different parameters for the base WHERE. In order words, can't we just always use your toWhereExpendeNUllExpression for good? IMHO, it won't hurt H2, MariaDB or PostgreSQL. But this will require a bit more testing and performance analysis.

That was more a decision taken in order to not affect other dialects.

#65 Updated by Alexandru Lungu 12 months ago

Eduard, in toWhereExpendedNullExpression, isn't the last parenthesis unbalanced? You have whereClause.append(" or (");, but whereClause.append(")"); once and whereClause.append(")"); a second time.

#66 Updated by Alexandru Lungu 12 months ago

Ah nevermind, is the one added at the beginning for withNull.

#67 Updated by Alexandru Lungu 12 months ago

I reviewed the changes and toWhereExpendedNullExpression seems safe for all dialects. The things that drove the COALESCE change were that ? > p or <augment> may be an undefined operation on NULL data type. I mean, ? > p technically evaluates to NULL if ? or p are NULL, but NULL or <augment> is quite strangely evaluating to <augment> (just like NULL is false). This applies to AND as well.

So we were unreliably using OR and AND so that:
  • TRUE OR NULL should be TRUE
  • FALSE OR NULL should be FALSE or NULL
  • NULL OR NULL should be FALSE or NULL
  • TRUE AND NULL should be FALSE or NULL
  • FALSE AND NULL should be FALSE or NULL
  • NULL AND NULL should be FALSE or NULL
  • Bonus: where null should work, but not select that respective row.
  • Do not rely on NOT.

Is this something that is guaranteed by SQL standards; I don't think so. But it is quite widely supported.

For SQL Server (https://learn.microsoft.com/en-us/sql/t-sql/language-elements/or-transact-sql?view=sql-server-ver17), OR and AND seems to respect this requirement.
For PostgreSQL (https://www.postgresql.org/docs/current/functions-logical.html), OR and AND seem to respect this requirement.
For MariaDB (https://mariadb.com/docs/server/reference/sql-structure/operators/logical-operators/or), OR and AND seem to respect this requirement.
For H2, I don't have the required documentation, but the ConditionAndOr code seems not to respect this requirement:

case OR: {
   if (l.getBoolean()) {
      return ValueBoolean.TRUE;
   }
   r = right.getValue(session);
   if (r.getBoolean()) {
      return ValueBoolean.TRUE;
   }
   if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) {
      return ValueNull.INSTANCE;
   }
   return ValueBoolean.FALSE;
}

For COALESCE, we don't assume any behaviors of AND and OR, but just the sole existence of such construct and of the boolean expressions. PostgreSQL, MariaDB, SQLServer and H2 do support this construct, but SQLServer does not support boolean expressions.

The good part is that FWD-H2 is in-house and we can change the AND/OR behavior to match the the "null as falsy" invariant we need. Do we have time for that?

#68 Updated by Constantin Asofiei 12 months ago

Alexandru, is this something which prevents 9456b to be merged to trunk? Does it affect other dialects than SQLServer?

#69 Updated by Alexandru Lungu 12 months ago

Constantin Asofiei wrote:

Alexandru, is this something which prevents 9456b to be merged to trunk? Does it affect other dialects than SQLServer?

It is functionally OK from my POV. I am OK to postpone work on #9456-67. In the current state, we will have a slower job in testing invalidation of queries mostly because SQLServer will generate different SQL then other dialects. And I do not mean the 1 = 1 instead of true due to the lack of boolean, but because of COALESCE vs OR/AND syntax.

isLimitBeforeOffset doesn't imply that the ORDER BY is mandatory. Other (unsupported) dialects might allow limit before offset without enforcing order by. From my POV, these two principles are distinct. Please use a isOrderByMandatory or something like that instead - with the risk of bloating the dialect, I favor clarity :)

This is the only point to still be assessed.

#70 Updated by Ovidiu Maxiniuc 12 months ago

I am about to alter 9456b and address the following 5 occurrences of current isLimitBeforeOffset():
  • FQLHelper.createBundle() -> isOrderByMandatory(); (new method)
  • FQLHelper.fillBundleLists() -> supportsBooleanDatatype(); (coalesce issue)
  • PreselectQuery.isOrderByRequired -> isOrderByMandatory(); (new, same as above)
  • RandomAccessQuery.executeImpl() -> supportsBooleanDatatype()
  • Query.createSqlQuery() -> remains as it is.

#71 Updated by Ovidiu Maxiniuc 12 months ago

Changes applied. Pushed up to revision 16106 after rebase to latest trunk.

Please review. I think we need to redo the runtime regression tests again, just to be on safe side.

#72 Updated by Eduard Soltan 12 months ago

Ovidiu Maxiniuc wrote:

Please review. I think we need to redo the runtime regression tests again, just to be on safe side.

Run again chui regression tests again, and it went successfully.

#73 Updated by Tomasz Domin 11 months ago

Alexandru Lungu wrote:

I added Tomasz as I watcher. Tomasz, can you review 9456b (SortCriterion.toWhereExpendeNUllExpression)? Can we rather use such AND/OR approach for all dialects and eventually replace the OG SortCriterion.toWhereExpression with this implementation entirely? The over-complication of having different parameter indices and different bundles for different dialects (especially for dirty H2 vs persistent SQLServer) seems unreasonable.

I've reviewed SortCriterion.toWhereExpendeNUllExpression and logically it seems identical to SortCriterion.toWhereExpression, except it requires tripling of column reference - instead of doubling. I guess it should not be a problem for a modern database.

Which one is better in the end - I dont know :), I liked COALESCE as it allows for a special comparison which includes NULLs - for databases supported until now.
I guess it needs to be checked with each database engine's query optimization engine - COALESCE had this problem where indices were not selected when expected and full table scan was executed instead.

#74 Updated by Constantin Asofiei 6 months ago

Ovidiu Maxiniuc wrote:

I am about to alter 9456b and address the following 5 occurrences of current isLimitBeforeOffset():

Ovidiu, do we still need 9456b?

#75 Updated by Ovidiu Maxiniuc 6 months ago

No. I checked the branch and all changes are found at least in 9457c. I archived the branch as 'dead'.

#76 Updated by Constantin Asofiei 6 months ago

Ovidiu Maxiniuc wrote:

No. I checked the branch and all changes are found at least in 9457c. I archived the branch as 'dead'.

Please unarchive it, rebase it and if there is nothing else remaining (all changes are in trunk), then it can be archived as 'dead'. Otherwise we need to keep it.

#77 Updated by Ovidiu Maxiniuc 6 months ago

OK, I reverted the archive and rebased the branch. After fixing a lots of conflicts (some revisions were reverted inside 9456b), the final revision (16361) is identical to current trunk.
Therefore it was re-archived again as dead.

Also available in: Atom PDF