QOQ distinct with Datetime column

After an upgrade to Lucee 6, we get a strange result if we are using a QOQ with a distinct to a datetime field.

I been able to reproduce it with that code

var rsData = queryNew( "scheduleLabel,scheduleTime", "varchar,datetime");
for(var i=1; i<= 50; i++) {
	var scheduleTime  = createDateTime(2024, 12, 12, i mod 12, 0, 0,0)
	rsData.addRow({scheduleLabel: "Event : #i mod 12#", scheduleTime: scheduleTime});
}

var rsUniqueDataTime = queryExecute(
    sql = "SELECT DISTINCT scheduleTime FROM rsData ORDER BY scheduleTime",
    options = {
        dbtype = 'query'
    }
);
 
writeDump(var=rsData, label="rsData");
writeDump(var=rsUniqueDataTime, label="rsUniqueDataTime");

As you can see, the distinct don’t return the result expected
image

OS: Windows 10 (10.0) 64bit
Servlet Container WildFly / Undertow - 2.2.24.Final
Java Version: 11.0.19 (Eclipse Adoptium) 64bit
Lucee Version: 6.0.3.1

Hi,

To narrow down the problem, does it do the same thing using GROUP BY instead of DISTINCT?

Yes I have a result similar as you can see from the screenshot. The result is random too. I presume that caused by the parallelism put in place under Lucee 6.

image

Here seems works fine.
I’m using Lucee 6.0.0+SNAPSHOT+451 (by TryCF)

dump(now());
var q = QueryNew("label,thisDate", "varchar,datetime");

cfloop( from=1, to=20, index="index") {

    var addValue = 10;

    if ( index > 7 AND index < 15 ) {
        var addValue = 20;
    }

    if ( index > 15 ) {
        var addValue = 50;
    }

    QueryAddRow( 
        q, 
        { thisDate: DateAdd( "n", addValue, now()), label: "index: #index# " }
    );

}

<cfdump var="#q#">

<cfquery dbtype="query" name="j">
    SELECT distinct thisDate
    FROM q 
    ORDER BY thisDate
</cfquery>

<cfdump var="#j#">

I try your code from my side. You are right that not causing issue but if I use more rows. ex: 200 instead of your 20 rows. I got that result.

image

Based on Improving Lucee's QoQ Support Again- now 200% faster

By default, any QoQ on a query object less than 50 rows will execute sequentially (no threads) because the overhead of managing the joining the threads is normally more than the benifit. 50+ rows seems to be where the benifit outweighs the overhead, so all query objects with that least that many rows will be processed in parallel.

I have maybe the impression that could be related to it.

If I set the lucee variable lucee.qoq.parallelism=9999999. I don’t have the issue anymore.

2 Likes

@Brad_Wood Do you think the issue could be related to the change made for the improvement of QoQ?

Interesting. Does this ONLY happen with datetime columns, or any type?

The partitioning SHOULD be thread safe. The grouped data is stored in a current hashmap (thread safe)

and the logic to add a new row uses computeIfAbsent() which is advertised as an atomic operation, ensuring internal locking as necessary.

Is it possible your dates have a millisecond component to them which is not reflected in your output, but makes then not equal to each other?

I can’t seem to reproduce this on trycf (running Lucee 6.1.0.243) even with 1000 rows in the query

I try with different type. I never been able to reproduce it with an other type. I even change my original query and cast it as a varchar and that was resolving the issue too.

My colleague had the same idea. We valid it the millisecond are equal. The result can be random even if the query return the same values and have an order on a Primary key.

I wonder if the algorithm which creates the unique key for each row is serializing the dates differently. It involves the to string casting and, if the value is longer than 255 chars, an MD5 hash. I don’t think the hash would kick in for a date, but it’s possible the string caster isn’t giving back the same value for each one. I need to be able to reproduce it though, and as I posted above, it’s not reproducing for me on trycf.

You just give me an hint with your MD5. I been able to reproduce it under your trycf by adding at the begin these 2 lines

settimezone(“Europe/Paris”);
SetLocale(“French (Standard)”);

I presume the cast of the datetime is different depending of the timezone/locale ?

Wow, nice find! I assume there’s something related with the string caster casting dates with a time zone that maybe isn’t thread safe. I haven’t been able to pinpoint that just yet. I’m not 100% sure which code path is being followed to convert the dates into strings for the partition keys behind the scenes.