Repository: squashql/squashql
Pull request #278 (2024-02-22 15:58:08)

Fix ParameterMeasure s13n issue


Patch:
diff --git a/bigquery/pom.xml b/bigquery/pom.xml
index e59b222..9cb323d 100644
--- a/bigquery/pom.xml
+++ b/bigquery/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/clickhouse/pom.xml b/clickhouse/pom.xml
index 639d1f0..bfc8c18 100644
--- a/clickhouse/pom.xml
+++ b/clickhouse/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/core/pom.xml b/core/pom.xml
index eaa8dc7..1a921fc 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/core/src/main/java/io/squashql/jackson/ParametrizedMeasureDeserializer.java b/core/src/main/java/io/squashql/jackson/ParametrizedMeasureDeserializer.java
index ced7115..61f61b8 100644
--- a/core/src/main/java/io/squashql/jackson/ParametrizedMeasureDeserializer.java
+++ b/core/src/main/java/io/squashql/jackson/ParametrizedMeasureDeserializer.java
@@ -10,6 +10,8 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
+import static io.squashql.jackson.JacksonUtil.OBJECT_MAPPER;
+
 @NoArgsConstructor
 public class ParametrizedMeasureDeserializer extends JsonDeserializer<ParametrizedMeasure> {
 
@@ -28,7 +30,7 @@ public class ParametrizedMeasureDeserializer extends JsonDeserializer<Parametriz
       if (javaType == null) {
         throw new IllegalArgumentException("Incorrect key " + propertyKey + ". Expected keys are: " + parameterTypes.keySet());
       }
-      parameters.put(propertyKey, JacksonUtil.OBJECT_MAPPER.readerFor(javaType).readValue(property.getValue()));
+      parameters.put(propertyKey, OBJECT_MAPPER.readerFor(javaType).readValue(property.getValue()));
     }
 
     String alias = node.get("alias").asText();
diff --git a/core/src/main/java/io/squashql/jackson/ParametrizedMeasureSerializer.java b/core/src/main/java/io/squashql/jackson/ParametrizedMeasureSerializer.java
new file mode 100644
index 0000000..c0155a5
--- /dev/null
+++ b/core/src/main/java/io/squashql/jackson/ParametrizedMeasureSerializer.java
@@ -0,0 +1,50 @@
+package io.squashql.jackson;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import io.squashql.query.measure.ParametrizedMeasure;
+import io.squashql.query.measure.Repository;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static io.squashql.jackson.JacksonUtil.OBJECT_MAPPER;
+
+@NoArgsConstructor
+public class ParametrizedMeasureSerializer extends JsonSerializer<ParametrizedMeasure> {
+
+  @Override
+  public void serializeWithType(ParametrizedMeasure value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
+    serialize(value, gen, serializers);
+  }
+
+  @Override
+  public void serialize(ParametrizedMeasure value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+    gen.writeStartObject();
+    gen.writeStringField(JsonTypeInfo.Id.CLASS.getDefaultPropertyName(), value.getClass().getName());
+    gen.writeStringField("key", value.key);
+    gen.writeStringField("alias", value.alias);
+
+    Map<String, JavaType> parameterTypes = new HashMap<>();
+    Repository.getParameterTypes(value.key).forEach(pt -> parameterTypes.put(pt.getOne(), pt.getTwo()));
+    gen.writeFieldName("parameters");
+    gen.writeStartObject();
+    for (Map.Entry<String, Object> entry : value.parameters.entrySet()) {
+      String propertyKey = entry.getKey();
+      JavaType javaType = parameterTypes.get(propertyKey);
+      if (javaType == null) {
+        throw new IllegalArgumentException("Incorrect key " + propertyKey + ". Expected keys are: " + parameterTypes.keySet());
+      }
+      gen.writeFieldName(propertyKey);
+      OBJECT_MAPPER.writerFor(javaType).writeValue(gen, entry.getValue());
+    }
+    gen.writeEndObject();
+    gen.writeEndObject();
+  }
+}
diff --git a/core/src/main/java/io/squashql/query/QueryExecutor.java b/core/src/main/java/io/squashql/query/QueryExecutor.java
index a4e21d8..5ac4a94 100644
--- a/core/src/main/java/io/squashql/query/QueryExecutor.java
+++ b/core/src/main/java/io/squashql/query/QueryExecutor.java
@@ -6,7 +6,6 @@ import io.squashql.query.database.DatabaseQuery;
 import io.squashql.query.database.QueryEngine;
 import io.squashql.query.database.SqlUtils;
 import io.squashql.query.dto.*;
-import io.squashql.query.dto.QueryJoinDto;
 import io.squashql.query.join.ExperimentalQueryMergeExecutor;
 import io.squashql.query.parameter.QueryCacheParameter;
 import io.squashql.table.ColumnarTable;
diff --git a/core/src/main/java/io/squashql/query/compiled/Evaluator.java b/core/src/main/java/io/squashql/query/compiled/Evaluator.java
index e1cdfcd..945ea90 100644
--- a/core/src/main/java/io/squashql/query/compiled/Evaluator.java
+++ b/core/src/main/java/io/squashql/query/compiled/Evaluator.java
@@ -173,7 +173,7 @@ public class Evaluator implements BiConsumer<QueryPlanNodeKey, ExecutionContext>
       columnValues.add(readTable.getColumnValues(alias));
     }
 
-    List<Object> vectorValues = ListUtils.createListWithNulls((int) readTable.count());
+    List<Object> vectorValues = ListUtils.createListWithNulls((int) writeToTable.count());
     writeToTable.pointDictionary().forEach((point, index) -> {
       int position = readTable.pointDictionary().getPosition(point);
       if (position >= 0) {
diff --git a/core/src/main/java/io/squashql/query/measure/ParametrizedMeasure.java b/core/src/main/java/io/squashql/query/measure/ParametrizedMeasure.java
index 8fce624..8f4e44c 100644
--- a/core/src/main/java/io/squashql/query/measure/ParametrizedMeasure.java
+++ b/core/src/main/java/io/squashql/query/measure/ParametrizedMeasure.java
@@ -1,7 +1,9 @@
 package io.squashql.query.measure;
 
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.squashql.jackson.ParametrizedMeasureDeserializer;
+import io.squashql.jackson.ParametrizedMeasureSerializer;
 import io.squashql.query.Measure;
 import lombok.AllArgsConstructor;
 import lombok.EqualsAndHashCode;
@@ -15,6 +17,7 @@ import java.util.Map;
 @NoArgsConstructor // For Jackson
 @AllArgsConstructor
 @JsonDeserialize(using = ParametrizedMeasureDeserializer.class)
+@JsonSerialize(using = ParametrizedMeasureSerializer.class)
 public class ParametrizedMeasure implements Measure {
 
   public String alias;
diff --git a/core/src/main/java/io/squashql/query/measure/ParametrizedMeasureFactory.java b/core/src/main/java/io/squashql/query/measure/ParametrizedMeasureFactory.java
index ffc8a30..2c94c71 100644
--- a/core/src/main/java/io/squashql/query/measure/ParametrizedMeasureFactory.java
+++ b/core/src/main/java/io/squashql/query/measure/ParametrizedMeasureFactory.java
@@ -30,7 +30,7 @@ public final class ParametrizedMeasureFactory {
     Function<List<Object>, Object> transformer = (tuple) -> {
       Lists.DoubleList prices = (Lists.DoubleList) tuple.get(0);
       Lists.LocalDateList dates = (Lists.LocalDateList) tuple.get(1);
-      int[] sort = MultipleColumnsSorter.sort(Collections.singletonList(dates), Collections.singletonList(naturalOrder()), new int[0]);
+      int[] sort = MultipleColumnsSorter.sort(Collections.singletonList(prices), Collections.singletonList(naturalOrder()), new int[0]);
 
       List<Double> orderedPrices = reorder(prices, sort);
       List<LocalDate> orderedDates = reorder(dates, sort);
@@ -54,19 +54,24 @@ public final class ParametrizedMeasureFactory {
             null);
 
     BiFunction<Object, Object, Object> comparisonOperator = (currentValue, parentValue) -> {
+      if (currentValue == null || parentValue == null) {
+        return null;
+      }
       List<Double> current = orderTupleOfList(currentValue).getOne();
       List<Double> parent = orderTupleOfList(parentValue).getOne();
 
       int size = parent.size();
       var index = (int) Math.floor(size * (1 - quantile));
-      var varParentWithCurrent = parent.get(index);
       List<Double> minus = new ArrayList<>(size);
       for (int i = 0; i < size; i++) {
-        minus.add(parent.get(0) - current.get(0));
+        minus.add(parent.get(i) - current.get(i));
       }
 
       Collections.sort(minus);
       var varParentWithoutCurrent = minus.get(index);
+      List<Double> parentVector = new ArrayList<>(parent);
+      Collections.sort(parentVector);
+      var varParentWithCurrent = parentVector.get(index);
 
       return varParentWithCurrent - varParentWithoutCurrent;
     };
diff --git a/duckdb/pom.xml b/duckdb/pom.xml
index 676f463..809f7ec 100644
--- a/duckdb/pom.xml
+++ b/duckdb/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
@@ -28,6 +28,11 @@
       <artifactId>duckdb_jdbc</artifactId>
       <version>${duckdb.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <scope>provided</scope>
+    </dependency>
 
     <dependency>
       <groupId>io.squashql</groupId>
diff --git a/duckdb/src/main/java/io/squashql/DuckDBDatastore.java b/duckdb/src/main/java/io/squashql/DuckDBDatastore.java
index 8c0cd6f..41e1291 100644
--- a/duckdb/src/main/java/io/squashql/DuckDBDatastore.java
+++ b/duckdb/src/main/java/io/squashql/DuckDBDatastore.java
@@ -4,6 +4,7 @@ import com.google.common.base.Suppliers;
 import io.squashql.jdbc.JdbcDatastore;
 import io.squashql.jdbc.JdbcUtil;
 import io.squashql.store.Store;
+import lombok.extern.slf4j.Slf4j;
 import org.duckdb.DuckDBConnection;
 
 import java.sql.Connection;
@@ -12,6 +13,7 @@ import java.sql.SQLException;
 import java.util.Map;
 import java.util.function.Supplier;
 
+@Slf4j
 public class DuckDBDatastore implements JdbcDatastore {
 
   private final DuckDBConnection connection;
@@ -28,6 +30,7 @@ public class DuckDBDatastore implements JdbcDatastore {
       this.connection = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
       this.stores = cacheMetadata ? Suppliers.memoize(this::fetchStoresByName) : this::fetchStoresByName;
     } catch (Exception e) {
+      log.error("Connection error", e);
       throw new RuntimeException(e);
     }
   }
diff --git a/java-client/pom.xml b/java-client/pom.xml
index 86ee921..1494afb 100644
--- a/java-client/pom.xml
+++ b/java-client/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <artifactId>squashql-parent</artifactId>
     <groupId>io.squashql</groupId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/js/typescript-library/package-lock.json b/js/typescript-library/package-lock.json
index 8917bb2..c33891c 100644
--- a/js/typescript-library/package-lock.json
+++ b/js/typescript-library/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@squashql/squashql-js",
-  "version": "1.12.0",
+  "version": "1.12.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@squashql/squashql-js",
-      "version": "1.12.0",
+      "version": "1.12.1",
       "license": "Apache-2.0",
       "dependencies": {
         "axios": "^1.6.7"
diff --git a/js/typescript-library/package.json b/js/typescript-library/package.json
index 044bed0..c0d2c39 100644
--- a/js/typescript-library/package.json
+++ b/js/typescript-library/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@squashql/squashql-js",
-  "version": "1.12.0",
+  "version": "1.12.1",
   "description": "SquashQL query builder/helper",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
diff --git a/js/typescript-library/pom.xml b/js/typescript-library/pom.xml
index 7cdf496..c2a00cc 100644
--- a/js/typescript-library/pom.xml
+++ b/js/typescript-library/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
     <relativePath>../../pom.xml</relativePath>
   </parent>
 
diff --git a/pom.xml b/pom.xml
index 42492a1..75dc83f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
   <groupId>io.squashql</groupId>
   <artifactId>squashql-parent</artifactId>
-  <version>1.12.1-SNAPSHOT</version>
+  <version>1.12.2-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>SquashQL</name>
diff --git a/server/pom.xml b/server/pom.xml
index 3d5c0a9..7376a2c 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/snowflake/pom.xml b/snowflake/pom.xml
index 83bf935..6fc1cc8 100644
--- a/snowflake/pom.xml
+++ b/snowflake/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>
diff --git a/spark/pom.xml b/spark/pom.xml
index e4c187d..59dd78d 100644
--- a/spark/pom.xml
+++ b/spark/pom.xml
@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.squashql</groupId>
     <artifactId>squashql-parent</artifactId>
-    <version>1.12.1-SNAPSHOT</version>
+    <version>1.12.2-SNAPSHOT</version>
   </parent>
 
   <groupId>io.squashql</groupId>


Test patch:
diff --git a/core/src/test/java/io/squashql/jackson/TestJacksonUtil.java b/core/src/test/java/io/squashql/jackson/TestJacksonUtil.java
index d8461e1..377a6e1 100644
--- a/core/src/test/java/io/squashql/jackson/TestJacksonUtil.java
+++ b/core/src/test/java/io/squashql/jackson/TestJacksonUtil.java
@@ -1,9 +1,16 @@
 package io.squashql.jackson;
 
+import io.squashql.query.Field;
+import io.squashql.query.Measure;
+import io.squashql.query.TableField;
+import io.squashql.query.dto.QueryDto;
+import io.squashql.query.measure.ParametrizedMeasure;
+import io.squashql.query.measure.Repository;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import java.time.LocalDate;
+import java.util.Map;
 
 public class TestJacksonUtil {
 
@@ -14,4 +21,27 @@ public class TestJacksonUtil {
     LocalDate deserialize = JacksonUtil.deserialize(serialize, LocalDate.class);
     Assertions.assertThat(deserialize).isEqualTo(d);
   }
+
+  @Test
+  void testTableField() {
+    TableField tableField = new TableField("myTable.myField");
+    ParametrizedMeasure pm = new ParametrizedMeasure("var", Repository.VAR, Map.of("value", tableField));
+
+    QueryDto query = new QueryDto();
+    query.table("myTable");
+    query.withColumn(tableField);
+    query.withMeasure(pm);
+
+    String tableFieldSer = JacksonUtil.serialize(tableField);
+    Field tableFieldDes = JacksonUtil.deserialize(tableFieldSer, Field.class);
+    Assertions.assertThat(tableFieldDes).isEqualTo(tableField);
+
+    String pmSer = JacksonUtil.serialize(pm);
+    Measure pmDes = JacksonUtil.deserialize(pmSer, Measure.class);
+    Assertions.assertThat(pmDes).isEqualTo(pm);
+
+    String querySer = JacksonUtil.serialize(query);
+    QueryDto queryDes = JacksonUtil.deserialize(querySer, QueryDto.class);
+    Assertions.assertThat(queryDes).isEqualTo(query);
+  }
 }