22
33import static io .stargate .sgv2 .jsonapi .exception .ErrorFormatters .errFmtApiColumnDef ;
44import static io .stargate .sgv2 .jsonapi .exception .ErrorFormatters .errFmtCqlIdentifier ;
5+ import static io .stargate .sgv2 .jsonapi .exception .ErrorFormatters .errFmtJoin ;
56import static io .stargate .sgv2 .jsonapi .exception .ErrorFormatters .errVars ;
67import static io .stargate .sgv2 .jsonapi .util .JsonUtil .arrayNodeToVector ;
78
1617import io .stargate .sgv2 .jsonapi .service .schema .tables .ApiColumnDef ;
1718import io .stargate .sgv2 .jsonapi .service .schema .tables .ApiColumnDefContainer ;
1819import io .stargate .sgv2 .jsonapi .util .CqlIdentifierUtil ;
20+ import io .stargate .sgv2 .jsonapi .util .JsonUtil ;
1921import java .util .ArrayList ;
2022import java .util .List ;
2123import java .util .Map ;
@@ -31,10 +33,53 @@ public TableSortClauseBuilder(TableSchemaObject table) {
3133 @ Override
3234 protected SortClause buildClauseFromDefinition (ObjectNode sortNode ) {
3335 // First, resolve the paths to column definitions
34- List <SortExpressionDefinition > sortExprDefs = resolveColumns (sortNode );
35- final List <SortExpression > sortExpressions = new ArrayList <>();
36+ var sortExprDefs = resolveColumns (sortNode );
37+
38+ // Then split into "special" (vector/vectorize, lexical) and regular expressions
39+ var lexicalExprs = new ArrayList <SortExpressionDefinition >();
40+ var vectorExprs = new ArrayList <SortExpressionDefinition >();
41+ var regularExprs = new ArrayList <SortExpressionDefinition >();
42+
3643 for (SortExpressionDefinition sortExprDef : sortExprDefs ) {
37- sortExpressions .add (buildSortExpression (sortExprDef ));
44+ var column = sortExprDef .column ;
45+ switch (column .type ().typeName ()) {
46+ case VECTOR -> vectorExprs .add (sortExprDef );
47+ case ASCII , TEXT -> {
48+ if (sortExprDef .sortValue .isTextual ()) {
49+ lexicalExprs .add (sortExprDef );
50+ } else {
51+ regularExprs .add (sortExprDef );
52+ }
53+ }
54+ default -> regularExprs .add (sortExprDef );
55+ }
56+ }
57+
58+ // Lexical(s)? Must have but one expression, cannot be combined with other sorts
59+ // Ditto for vector/vectorize
60+ if (!lexicalExprs .isEmpty () || !vectorExprs .isEmpty ()) {
61+ if (sortExprDefs .size () > 1 ) {
62+ throw SortException .Code .CANNOT_SORT_ON_SPECIAL_WITH_OTHERS .get (
63+ errVars (
64+ schema ,
65+ map -> {
66+ map .put ("lexicalSorts" , columnsDesc (lexicalExprs ));
67+ map .put ("regularSorts" , columnsDesc (regularExprs ));
68+ map .put ("vectorSorts" , columnsDesc (vectorExprs ));
69+ }));
70+ }
71+
72+ if (!lexicalExprs .isEmpty ()) {
73+ return new SortClause (List .of (buildLexicalSortExpression (lexicalExprs .getFirst ())));
74+ }
75+
76+ return new SortClause (List .of (buildVectorOrVectorizeSortExpression (vectorExprs .getFirst ())));
77+ }
78+
79+ // Otherwise, we can build regular sort expression(s)
80+ final List <SortExpression > sortExpressions = new ArrayList <>();
81+ for (SortExpressionDefinition exprDef : regularExprs ) {
82+ sortExpressions .add (buildRegularSortExpression (exprDef ));
3883 }
3984 return new SortClause (sortExpressions );
4085 }
@@ -70,32 +115,98 @@ private List<SortExpressionDefinition> resolveColumns(ObjectNode sortNode) {
70115 return sortExprDefs ;
71116 }
72117
73- protected SortExpression buildSortExpression (SortExpressionDefinition exprDef ) {
74- final String path = exprDef .path ();
75- final JsonNode innerValue = exprDef .sortValue ();
118+ protected SortExpression buildLexicalSortExpression (SortExpressionDefinition lexicalExpr ) {
119+ // caller validated JsonNode is textual; for now nothing more to validate
120+ return SortExpression .tableLexicalSort (lexicalExpr .path (), lexicalExpr .sortValue .textValue ());
121+ }
76122
77- float [] vectorFloats = tryDecodeBinaryVector (path , innerValue );
123+ protected SortExpression buildVectorOrVectorizeSortExpression (
124+ SortExpressionDefinition vectorExpr ) {
125+ final String path = vectorExpr .path ();
126+ final JsonNode exprValue = vectorExpr .sortValue ();
78127
79- // handle table vector sort
128+ // So we know we have a Vector column; now can check if value is a binary vector or an array
129+ // of floats, or a string to vectorize.
130+ // For Vectorize, further checks are done in the TableSortClauseResolver.
131+
132+ // First: vector data either as EJSON binary or as JSON Array (of floats)
133+ float [] vectorFloats = tryDecodeBinaryVector (path , exprValue );
80134 if (vectorFloats != null ) {
81135 return SortExpression .tableVectorSort (path , vectorFloats );
82136 }
83- if (innerValue instanceof ArrayNode innerArray ) {
84- // TODO: HACK: quick support for tables, if the value is an array we will assume the
85- // column is a vector then need to check on table pathway that the sort is correct.
86- // NOTE: does not check if there are more than one sort expression, the
87- // TableSortClauseResolver will take care of that so we can get proper ApiExceptions
137+ if (exprValue instanceof ArrayNode innerArray ) {
88138 return SortExpression .tableVectorSort (path , arrayNodeToVector (innerArray ));
89139 }
90- if (innerValue .isTextual ()) {
91- // TODO: HACK: quick support for tables, if the value is an text we will assume the column
92- // is a vector and the user wants to do vectorize then need to check on table pathway that
93- // the sort is correct.
94- // NOTE: does not check if there are more than one sort expression, the
95- // TableSortClauseResolver will take care of that so we can get proper ApiExceptions
96- // this is also why we do not break the look here
97- return SortExpression .tableVectorizeSort (path , innerValue .textValue ());
140+
141+ // Otherwise, check if it is a String to vectorize
142+ if (exprValue .isTextual ()) {
143+ return SortExpression .tableVectorizeSort (path , exprValue .textValue ());
98144 }
99- return super .buildRegularSortExpression (path , innerValue );
145+
146+ // Otherwise, invalid (cannot be a regular sort as it is a Vector column)
147+ throw SortException .Code .INVALID_VECTOR_SORT_EXPRESSION .get (
148+ errVars (
149+ schema ,
150+ map -> {
151+ map .put ("jsonType" , JsonUtil .nodeTypeAsString (exprValue ));
152+ }));
153+ }
154+
155+ /**
156+ * Helper method to build a "non-special" sort expression for given definition; validates
157+ * expression value and builds the {@link SortExpression} object.
158+ */
159+ private SortExpression buildRegularSortExpression (SortExpressionDefinition exprDef ) {
160+ JsonNode sortValue = exprDef .sortValue ();
161+
162+ // First: valid cases
163+ if (sortValue .isInt ()) {
164+ // Yes, we have an integer value. But is it valid?
165+ if (sortValue .intValue () == 1 ) {
166+ return SortExpression .sort (exprDef .path (), true );
167+ }
168+ if (sortValue .intValue () == -1 ) {
169+ return SortExpression .sort (exprDef .path (), false );
170+ }
171+ } else if (sortValue .isArray ()) {
172+ // Special checking for ArrayNode and String to give less confusing error messages
173+
174+ throw SortException .Code .CANNOT_VECTOR_SORT_NON_VECTOR_COLUMNS .get (
175+ errVars (
176+ schema ,
177+ map -> {
178+ map .put (
179+ "vectorColumns" ,
180+ errFmtApiColumnDef (
181+ schema .apiTableDef ().allColumns ().filterVectorColumnsToList ()));
182+ map .put ("sortColumns" , exprDef .path ());
183+ }));
184+ } else if (sortValue .isTextual ()) {
185+ // We only end up here for non-TEXT/ASCII/VECTOR columns (other cases are handled
186+ // by caller and further validated later on)
187+ throw SortException .Code .CANNOT_VECTORIZE_SORT_NON_VECTOR_COLUMN .get (
188+ errVars (
189+ schema ,
190+ map -> {
191+ map .put (
192+ "vectorColumns" ,
193+ errFmtApiColumnDef (
194+ schema .apiTableDef ().allColumns ().filterVectorColumnsToList ()));
195+ map .put ("sortColumns" , exprDef .path ());
196+ }));
197+ }
198+
199+ // Otherwise general failure message wrt use of 1 or -1 for Regular sort expression
200+ throw SortException .Code .INVALID_REGULAR_SORT_EXPRESSION .get (
201+ errVars (
202+ schema ,
203+ map -> {
204+ map .put ("jsonExpr" , sortValue .toString ());
205+ map .put ("jsonType" , JsonUtil .nodeTypeAsString (sortValue ));
206+ }));
207+ }
208+
209+ private String columnsDesc (List <SortExpressionDefinition > sortExprDefs ) {
210+ return errFmtJoin (sortExprDefs .stream ().map (SortExpressionDefinition ::path ).toList ());
100211 }
101212}
0 commit comments