@@ -808,6 +808,341 @@ public QClause createFilterClause(@NotNull FieldKey fieldKey, Object value)
808808 }
809809 };
810810
811+ protected Collection getCollectionParam (Object value )
812+ {
813+ if (value instanceof Collection )
814+ {
815+ return (Collection )value ;
816+ }
817+ else
818+ {
819+ List <String > values = new ArrayList <>();
820+ if (value != null )
821+ values .addAll (parseParams (value , getValueSeparator (), isNewLineSeparatorAllowed ()));
822+ return values ;
823+ }
824+ }
825+
826+ /** TODO:
827+ * <xsd:enumeration value="arrayisempty"/>
828+ * <xsd:enumeration value="arrayisnotempty"/>
829+ */
830+ public static final CompareType ARRAY_CONTAINS_ALL = new CompareType ("Contains All" , "arraycontainsall" , "ARRAYCONTAINSALL" , true , null , OperatorType .ARRAYCONTAINSALL )
831+ {
832+ @ Override
833+ public ArrayContainsAllClause createFilterClause (@ NotNull FieldKey fieldKey , Object value )
834+ {
835+ return new ArrayContainsAllClause (fieldKey , getCollectionParam (value ));
836+ }
837+
838+ @ Override
839+ public boolean meetsCriteria (ColumnRenderProperties col , Object value , Object [] filterValues )
840+ {
841+ throw new UnsupportedOperationException ("Conditional formatting not yet supported for Multi Choices" );
842+ }
843+
844+ @ Override
845+ public String getValueSeparator ()
846+ {
847+ return ArrayClause .ARRAY_VALUE_SEPARATOR ;
848+ }
849+ };
850+
851+ public static final CompareType ARRAY_CONTAINS_ANY = new CompareType ("Contains Any" , "arraycontainsany" , "ARRAYCONTAINSANY" , true , null , OperatorType .ARRAYCONTAINSANY )
852+ {
853+ @ Override
854+ public ArrayContainsAnyClause createFilterClause (@ NotNull FieldKey fieldKey , Object value )
855+ {
856+ return new ArrayContainsAnyClause (fieldKey , getCollectionParam (value ));
857+ }
858+
859+ @ Override
860+ public boolean meetsCriteria (ColumnRenderProperties col , Object value , Object [] filterValues )
861+ {
862+ throw new UnsupportedOperationException ("Conditional formatting not yet supported for Multi Choices" );
863+ }
864+
865+ @ Override
866+ public String getValueSeparator ()
867+ {
868+ return ArrayClause .ARRAY_VALUE_SEPARATOR ;
869+ }
870+ };
871+
872+ public static final CompareType ARRAY_CONTAINS_NONE = new CompareType ("Contains None" , "arraycontainsnone" , "ARRAYCONTAINSNONE" , true , null , OperatorType .ARRAYCONTAINSNONE )
873+ {
874+ @ Override
875+ public ArrayContainsNoneClause createFilterClause (@ NotNull FieldKey fieldKey , Object value )
876+ {
877+ return new ArrayContainsNoneClause (fieldKey , getCollectionParam (value ));
878+ }
879+
880+ @ Override
881+ public boolean meetsCriteria (ColumnRenderProperties col , Object value , Object [] filterValues )
882+ {
883+ throw new UnsupportedOperationException ("Conditional formatting not yet supported for Multi Choices" );
884+ }
885+
886+ @ Override
887+ public String getValueSeparator ()
888+ {
889+ return ArrayClause .ARRAY_VALUE_SEPARATOR ;
890+ }
891+ };
892+
893+ public static final CompareType ARRAY_MATCHES = new CompareType ("Contains Exactly" , "arraymatches" , "ARRAYMATCHES" , true , null , OperatorType .ARRAYMATCHES )
894+ {
895+ @ Override
896+ public ArrayMatchesClause createFilterClause (@ NotNull FieldKey fieldKey , Object value )
897+ {
898+ return new ArrayMatchesClause (fieldKey , getCollectionParam (value ));
899+ }
900+
901+ @ Override
902+ public boolean meetsCriteria (ColumnRenderProperties col , Object value , Object [] filterValues )
903+ {
904+ throw new UnsupportedOperationException ("Conditional formatting not yet supported for Multi Choices" );
905+ }
906+
907+ @ Override
908+ public String getValueSeparator ()
909+ {
910+ return ArrayClause .ARRAY_VALUE_SEPARATOR ;
911+ }
912+ };
913+
914+ public static final CompareType ARRAY_NOT_MATCHES = new CompareType ("Does Not Contain Exactly" , "arraynotmatches" , "ARRAYNOTMATCHES" , true , null , OperatorType .ARRAYNOTMATCHES )
915+ {
916+ @ Override
917+ public ArrayNotMatchesClause createFilterClause (@ NotNull FieldKey fieldKey , Object value )
918+ {
919+ return new ArrayNotMatchesClause (fieldKey , getCollectionParam (value ));
920+ }
921+
922+ @ Override
923+ public boolean meetsCriteria (ColumnRenderProperties col , Object value , Object [] filterValues )
924+ {
925+ throw new UnsupportedOperationException ("Conditional formatting not yet supported for Multi Choices" );
926+ }
927+
928+ @ Override
929+ public String getValueSeparator ()
930+ {
931+ return ArrayClause .ARRAY_VALUE_SEPARATOR ;
932+ }
933+ };
934+
935+ public static abstract class ArrayClause extends SimpleFilter .MultiValuedFilterClause
936+ {
937+ public static final String ARRAY_VALUE_SEPARATOR = "," ;
938+
939+ public ArrayClause (@ NotNull FieldKey fieldKey , CompareType comparison , Collection <?> params , boolean negated )
940+ {
941+ super (fieldKey , comparison , params , negated );
942+ }
943+
944+ public SQLFragment [] getParamSQLFragments (SqlDialect dialect )
945+ {
946+ Object [] params = getParamVals ();
947+ SQLFragment [] fragments = new SQLFragment [params .length ];
948+
949+ JdbcType type = null ;
950+ // Try to infer the type from the first non-null parameter
951+ for (Object param : params )
952+ {
953+ if (param != null )
954+ {
955+ type = JdbcType .valueOf (param .getClass ());
956+ break ;
957+ }
958+ }
959+
960+ for (int i = 0 ; i < params .length ; i ++)
961+ fragments [i ] = new SQLFragment ().append (escapeLabKeySqlValue (params [i ], type ));
962+
963+ return fragments ;
964+ }
965+
966+ public Pair <SQLFragment , SQLFragment > getSqlFragments (Map <FieldKey , ? extends ColumnInfo > columnMap , SqlDialect dialect )
967+ {
968+ SQLFragment [] paramValues = getParamSQLFragments (dialect );
969+
970+ if (paramValues == null || paramValues .length == 0 )
971+ return null ;
972+
973+ ColumnInfo colInfo = columnMap != null ? columnMap .get (_fieldKey ) : null ;
974+ var alias = SimpleFilter .getAliasForColumnFilter (dialect , colInfo , _fieldKey );
975+
976+ SQLFragment valuesFragment = dialect .array_construct (paramValues );
977+ SQLFragment columnFragment = new SQLFragment ().appendIdentifier (alias );
978+
979+ return new Pair <>(valuesFragment , columnFragment );
980+ }
981+
982+ }
983+
984+ private static class ArrayContainsAllClause extends ArrayClause
985+ {
986+
987+ public ArrayContainsAllClause (@ NotNull FieldKey fieldKey , Collection <?> params )
988+ {
989+ super (fieldKey , CompareType .ARRAY_CONTAINS_ALL , params , false );
990+ }
991+
992+ @ Override
993+ public SQLFragment toSQLFragment (Map <FieldKey , ? extends ColumnInfo > columnMap , SqlDialect dialect )
994+ {
995+ Pair <SQLFragment , SQLFragment > valueFieldSql = getSqlFragments (columnMap , dialect );
996+ if (valueFieldSql == null )
997+ return new SQLFragment ("1=2" );
998+
999+ return dialect .array_all_in_array (valueFieldSql .first , valueFieldSql .second );
1000+ }
1001+
1002+ @ Override
1003+ public String getLabKeySQLWhereClause (Map <FieldKey , ? extends ColumnInfo > columnMap )
1004+ {
1005+ return "array_contains_all(" + getLabKeySQLColName (_fieldKey ) + ", " + getParamVals ()[0 ] + ")" ;
1006+ }
1007+
1008+ @ Override
1009+ public void appendFilterText (StringBuilder sb , ColumnNameFormatter formatter )
1010+ {
1011+ Object [] params = getParamVals ();
1012+ sb .append ("contains all of " ).append (Arrays .toString (params ));
1013+ }
1014+
1015+ }
1016+
1017+ private static class ArrayContainsAnyClause extends ArrayClause
1018+ {
1019+
1020+ public ArrayContainsAnyClause (@ NotNull FieldKey fieldKey , CompareType comparison , Collection <?> params , boolean negated )
1021+ {
1022+ super (fieldKey , comparison , params , negated );
1023+ }
1024+
1025+ public ArrayContainsAnyClause (@ NotNull FieldKey fieldKey , Collection <?> params )
1026+ {
1027+ this (fieldKey , CompareType .ARRAY_CONTAINS_ANY , params , false );
1028+ }
1029+
1030+ @ Override
1031+ public SQLFragment toSQLFragment (Map <FieldKey , ? extends ColumnInfo > columnMap , SqlDialect dialect )
1032+ {
1033+ Pair <SQLFragment , SQLFragment > valueFieldSql = getSqlFragments (columnMap , dialect );
1034+ if (valueFieldSql == null )
1035+ return new SQLFragment ("1=2" );
1036+
1037+ SQLFragment sql = dialect .array_some_in_array (valueFieldSql .first , valueFieldSql .second );
1038+ if (!_negated )
1039+ return sql ;
1040+ return new SQLFragment (" NOT (" ).append (sql ).append (")" );
1041+ }
1042+
1043+ @ Override
1044+ public String getLabKeySQLWhereClause (Map <FieldKey , ? extends ColumnInfo > columnMap )
1045+ {
1046+ return "array_contains_any(" + getLabKeySQLColName (_fieldKey ) + ", " + getParamVals ()[0 ] + ")" ;
1047+ }
1048+
1049+ @ Override
1050+ public void appendFilterText (StringBuilder sb , ColumnNameFormatter formatter )
1051+ {
1052+ Object [] params = getParamVals ();
1053+ sb .append ("contains at least one of " ).append (Arrays .toString (params ));
1054+ }
1055+
1056+ }
1057+
1058+ private static class ArrayContainsNoneClause extends ArrayContainsAnyClause
1059+ {
1060+
1061+ public ArrayContainsNoneClause (@ NotNull FieldKey fieldKey , Collection <?> params )
1062+ {
1063+ super (fieldKey , CompareType .ARRAY_CONTAINS_NONE , params , true );
1064+ }
1065+
1066+ @ Override
1067+ public String getLabKeySQLWhereClause (Map <FieldKey , ? extends ColumnInfo > columnMap )
1068+ {
1069+ return "array_contains_none(" + getLabKeySQLColName (_fieldKey ) + ", " + getParamVals ()[0 ] + ")" ;
1070+ }
1071+
1072+ @ Override
1073+ public void appendFilterText (StringBuilder sb , ColumnNameFormatter formatter )
1074+ {
1075+ Object [] params = getParamVals ();
1076+ sb .append ("contains none of " ).append (Arrays .toString (params ));
1077+ }
1078+
1079+ }
1080+
1081+ private static class ArrayMatchesClause extends ArrayClause
1082+ {
1083+
1084+ public ArrayMatchesClause (@ NotNull FieldKey fieldKey , CompareType comparison , Collection <?> params , boolean negated )
1085+ {
1086+ super (fieldKey , comparison , params , negated );
1087+ }
1088+
1089+ public ArrayMatchesClause (@ NotNull FieldKey fieldKey , Collection <?> params )
1090+ {
1091+ this (fieldKey , CompareType .ARRAY_MATCHES , params , false );
1092+ }
1093+
1094+ @ Override
1095+ public SQLFragment toSQLFragment (Map <FieldKey , ? extends ColumnInfo > columnMap , SqlDialect dialect )
1096+ {
1097+ Pair <SQLFragment , SQLFragment > valueFieldSql = getSqlFragments (columnMap , dialect );
1098+ if (valueFieldSql == null )
1099+ return new SQLFragment ("1=2" );
1100+
1101+ SQLFragment sql = dialect .array_same_array (valueFieldSql .first , valueFieldSql .second );
1102+ if (!_negated )
1103+ return sql ;
1104+ return new SQLFragment (" NOT (" ).append (sql ).append (")" );
1105+ }
1106+
1107+ @ Override
1108+ public String getLabKeySQLWhereClause (Map <FieldKey , ? extends ColumnInfo > columnMap )
1109+ {
1110+ return "array_is_same(" + getLabKeySQLColName (_fieldKey ) + ", " + getParamVals ()[0 ] + ")" ;
1111+ }
1112+
1113+ @ Override
1114+ public void appendFilterText (StringBuilder sb , ColumnNameFormatter formatter )
1115+ {
1116+ Object [] params = getParamVals ();
1117+ sb .append ("contains the same elements as " ).append (Arrays .toString (params ));
1118+ }
1119+
1120+ }
1121+
1122+ private static class ArrayNotMatchesClause extends ArrayMatchesClause
1123+ {
1124+
1125+ public ArrayNotMatchesClause (@ NotNull FieldKey fieldKey , Collection <?> params )
1126+ {
1127+ super (fieldKey , CompareType .ARRAY_NOT_MATCHES , params , true );
1128+ }
1129+
1130+ @ Override
1131+ public String getLabKeySQLWhereClause (Map <FieldKey , ? extends ColumnInfo > columnMap )
1132+ {
1133+ return "NOT array_is_same(" + getLabKeySQLColName (_fieldKey ) + ", " + getParamVals ()[0 ] + ")" ;
1134+ }
1135+
1136+ @ Override
1137+ public void appendFilterText (StringBuilder sb , ColumnNameFormatter formatter )
1138+ {
1139+ Object [] params = getParamVals ();
1140+ sb .append ("does not contain the same elements as " ).append (Arrays .toString (params ));
1141+ }
1142+
1143+ }
1144+
1145+
8111146 private static class QClause extends CompareType .CompareClause
8121147 {
8131148 private List <ColumnInfo > _queryColumns = null ;
0 commit comments