/
AbstractPrequeryGenerator.scala
1739 lines (1400 loc) · 95.3 KB
/
AbstractPrequeryGenerator.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright © 2015-2018 the contributors (see Contributors.md).
*
* This file is part of Knora.
*
* Knora is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Knora is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with Knora. If not, see <http://www.gnu.org/licenses/>.
*/
package org.knora.webapi.messages.util.search.gravsearch.prequery
import org.knora.webapi._
import org.knora.webapi.exceptions._
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.util.search._
import org.knora.webapi.messages.util.search.gravsearch.GravsearchQueryChecker
import org.knora.webapi.messages.util.search.gravsearch.types._
import org.knora.webapi.messages.v2.responder.valuemessages.DateValueContentV2
import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter}
import org.knora.webapi.util.ApacheLuceneSupport.LuceneQueryString
import scala.collection.mutable
object AbstractPrequeryGenerator {
// separator used by GroupConcat
val groupConcatSeparator: Char = StringFormatter.INFORMATION_SEPARATOR_ONE
}
/**
* An abstract base class for [[WhereTransformer]] instances that generate SPARQL prequeries from Gravsearch input.
*
* @param typeInspectionResult the result of running type inspection on the Gravsearch input.
* @param querySchema the ontology schema used in the input Gravsearch query.
*/
abstract class AbstractPrequeryGenerator(constructClause: ConstructClause,
typeInspectionResult: GravsearchTypeInspectionResult,
querySchema: ApiV2Schema) extends WhereTransformer {
protected implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance
// a Set containing all `TypeableEntity` (keys of `typeInspectionResult`) that have already been processed
// in order to prevent duplicates
private val processedTypeInformationKeysWhereClause = mutable.Set.empty[TypeableEntity]
// suffix appended to variables that are returned by a SPARQL aggregation function.
protected val groupConcatVariableSuffix = "__Concat"
// A set of types that can be treated as dates by the knora-api:toSimpleDate function.
private val dateTypes: Set[IRI] = Set(OntologyConstants.KnoraApiV2Complex.DateValue, OntologyConstants.KnoraApiV2Complex.StandoffTag)
/**
* A container for a generated variable representing a value literal.
*
* @param variable the generated variable.
* @param useInOrderBy if `true`, the generated variable can be used in ORDER BY.
*/
private case class GeneratedQueryVariable(variable: QueryVariable, useInOrderBy: Boolean)
// variables that are created when processing filter statements or for a value object var used as a sort criterion
// they represent the value of a literal pointed to by a value object
private val valueVariablesAutomaticallyGenerated = mutable.Map.empty[QueryVariable, Set[GeneratedQueryVariable]]
// variables the represent resource metadata
private val resourceMetadataVariables = mutable.Set.empty[QueryVariable]
// The query can set this to false to disable inference.
var useInference = true
/**
* Saves a generated variable representing a value literal, if it hasn't been saved already.
*
* @param valueVar the variable representing the value.
* @param generatedVar the generated variable representing the value literal.
* @param useInOrderBy if `true`, the generated variable can be used in ORDER BY.
* @return `true` if the generated variable was saved, `false` if it had already been saved.
*/
private def addGeneratedVariableForValueLiteral(valueVar: QueryVariable, generatedVar: QueryVariable, useInOrderBy: Boolean = true): Boolean = {
val currentGeneratedVars = valueVariablesAutomaticallyGenerated.getOrElse(valueVar, Set.empty[GeneratedQueryVariable])
if (!currentGeneratedVars.exists(currentGeneratedVar => currentGeneratedVar.variable == generatedVar)) {
valueVariablesAutomaticallyGenerated.put(valueVar, currentGeneratedVars + GeneratedQueryVariable(generatedVar, useInOrderBy))
true
} else {
false
}
}
/**
* Gets a saved generated variable representing a value literal, for use in ORDER BY.
*
* @param valueVar the variable representing the value.
* @return a generated variable that represents a value literal and can be used in ORDER BY, or `None` if no such variable has been saved.
*/
protected def getGeneratedVariableForValueLiteralInOrderBy(valueVar: QueryVariable): Option[QueryVariable] = {
valueVariablesAutomaticallyGenerated.get(valueVar) match {
case Some(generatedVars: Set[GeneratedQueryVariable]) =>
val generatedVarsForOrderBy: Set[QueryVariable] = generatedVars.filter(_.useInOrderBy).map(_.variable)
if (generatedVarsForOrderBy.size > 1) {
throw AssertionException(s"More than one variable was generated for the literal values of ${valueVar.toSparql} and marked for use in ORDER BY: ${generatedVarsForOrderBy.map(_.toSparql).mkString(", ")}")
}
generatedVarsForOrderBy.headOption
case None => None
}
}
// Generated statements for date literals, so we don't generate the same statements twice.
private val generatedDateStatements = mutable.Set.empty[StatementPattern]
// Variables generated to represent marked-up text in standoff, so we don't generate the same variables twice.
private val standoffMarkedUpVariables = mutable.Set.empty[QueryVariable]
/**
* The variable in the CONSTRUCT clause that represents the main resource.
*/
val mainResourceVariable: QueryVariable = {
val mainResourceQueryVariables = constructClause.statements.foldLeft(Set.empty[QueryVariable]) {
case (acc: Set[QueryVariable], statementPattern) =>
statementPattern.pred match {
case IriRef(iri, _) =>
val iriStr = iri.toString
if (iriStr == OntologyConstants.KnoraApiV2Simple.IsMainResource || iriStr == OntologyConstants.KnoraApiV2Complex.IsMainResource) {
statementPattern.obj match {
case XsdLiteral(value, SmartIri(OntologyConstants.Xsd.Boolean)) if value.toBoolean =>
statementPattern.subj match {
case queryVariable: QueryVariable => acc + queryVariable
case _ => throw GravsearchException(s"The subject of knora-api:isMainResource must be a variable")
}
case _ => acc
}
} else {
acc
}
case _ => acc
}
}
if (mainResourceQueryVariables.isEmpty) {
throw GravsearchException("CONSTRUCT clause contains no knora-api:isMainResource")
}
if (mainResourceQueryVariables.size > 1) {
throw GravsearchException("CONSTRUCT clause contains more than one knora-api:isMainResource")
}
mainResourceQueryVariables.head
}
/**
* Creates additional statements for a non property type (e.g., a resource).
*
* @param nonPropertyTypeInfo type information about non property type.
* @param inputEntity the [[Entity]] to make the statements about.
* @return a sequence of [[QueryPattern]] representing the additional statements.
*/
private def createAdditionalStatementsForNonPropertyType(nonPropertyTypeInfo: NonPropertyTypeInfo, inputEntity: Entity): Seq[QueryPattern] = {
if (nonPropertyTypeInfo.isResourceType) {
// inputEntity is either source or target of a linking property
// create additional statements in order to query permissions and other information for a resource
Seq(
StatementPattern.makeExplicit(subj = inputEntity, pred = IriRef(OntologyConstants.KnoraBase.IsDeleted.toSmartIri), obj = XsdLiteral(value = "false", datatype = OntologyConstants.Xsd.Boolean.toSmartIri))
)
} else {
// inputEntity is target of a value property
// properties are handled by `convertStatementForPropertyType`, no processing needed here
Seq.empty[QueryPattern]
}
}
/**
* Generates statements matching a `knora-base:LinkValue`.
*
* @param linkSource the resource that is the source of the link.
* @param linkPred the link predicate.
* @param linkTarget the resource that is the target of the link.
* @return statements matching the `knora-base:LinkValue` that describes the link.
*/
private def generateStatementsForLinkValue(linkSource: Entity, linkPred: Entity, linkTarget: Entity): Seq[StatementPattern] = {
// Generate a variable name representing the link value
val linkValueObjVar: QueryVariable = SparqlTransformer.createUniqueVariableFromStatementForLinkValue(
baseStatement = StatementPattern(
subj = linkSource,
pred = linkPred,
obj = linkTarget
)
)
// create an Entity that connects the subject of the linking property with the link value object
val linkValueProp: Entity = linkPred match {
case linkingPropQueryVar: QueryVariable =>
// Generate a variable name representing the link value property
// in case FILTER patterns are given restricting the linking property's possible IRIs, the same variable will recreated when processing FILTER patterns
createLinkValuePropertyVariableFromLinkingPropertyVariable(linkingPropQueryVar)
case propIri: IriRef =>
// convert the given linking property IRI to the corresponding link value property IRI
// only matches the linking property's link value
IriRef(propIri.iri.toOntologySchema(InternalSchema).fromLinkPropToLinkValueProp)
case literal: XsdLiteral => throw GravsearchException(s"literal ${literal.toSparql} cannot be used as a predicate")
case other => throw GravsearchException(s"${other.toSparql} cannot be used as a predicate")
}
// Add statements that represent the link value's properties for the given linking property
// do not check for the predicate because inference would not work
// instead, linkValueProp restricts the link value objects to be returned
Seq(
StatementPattern.makeInferred(subj = linkSource, pred = linkValueProp, obj = linkValueObjVar),
StatementPattern.makeExplicit(subj = linkValueObjVar, pred = IriRef(OntologyConstants.Rdf.Type.toSmartIri), obj = IriRef(OntologyConstants.KnoraBase.LinkValue.toSmartIri)),
StatementPattern.makeExplicit(subj = linkValueObjVar, pred = IriRef(OntologyConstants.KnoraBase.IsDeleted.toSmartIri), obj = XsdLiteral(value = "false", datatype = OntologyConstants.Xsd.Boolean.toSmartIri)),
StatementPattern.makeExplicit(subj = linkValueObjVar, pred = IriRef(OntologyConstants.Rdf.Subject.toSmartIri), obj = linkSource),
StatementPattern.makeExplicit(subj = linkValueObjVar, pred = IriRef(OntologyConstants.Rdf.Object.toSmartIri), obj = linkTarget)
)
}
private def convertStatementForPropertyType(inputOrderBy: Seq[OrderCriterion])(propertyTypeInfo: PropertyTypeInfo, statementPattern: StatementPattern, typeInspectionResult: GravsearchTypeInspectionResult): Seq[QueryPattern] = {
/**
* Ensures that if the object of a statement is a variable, and is used in the ORDER BY clause of the input query, the subject of the statement
* is the main resource. Throws an exception otherwise.
*
* @param objectVar the variable that is the object of the statement.
*/
def checkSubjectInOrderBy(objectVar: QueryVariable): Unit = {
statementPattern.subj match {
case subjectVar: QueryVariable =>
if (mainResourceVariable != subjectVar && inputOrderBy.exists(criterion => criterion.queryVariable == objectVar)) {
throw GravsearchException(s"Variable ${objectVar.toSparql} is used in ORDER BY, but does not represent a value of the main resource")
}
case _ => ()
}
}
/**
* Transforms a statement pointing to a list node so it matches also any of its subnodes.
*
* @return transformed statements.
*/
def handleListNode(): Seq[StatementPattern] = {
if (querySchema == ApiV2Simple) {
throw GravsearchException("the method 'handleListNode' only works for the complex schema")
}
// the list node to match for provided in the input query
val listNode: Entity = statementPattern.obj
// variable representing the list node to match for
val listNodeVar: QueryVariable = SparqlTransformer.createUniqueVariableFromStatement(
baseStatement = statementPattern,
suffix = "listNodeVar"
)
// transforms the statement given in the input query so the list node and any of its subnodes are matched
Seq(
statementPatternToInternalSchema(statementPattern, typeInspectionResult).copy(obj = listNodeVar),
StatementPattern.makeExplicit(
subj = listNode,
pred = IriRef(iri = OntologyConstants.KnoraBase.HasSubListNode.toSmartIri, propertyPathOperator = Some('*')),
obj = listNodeVar
)
)
}
val (maybeSubjectTypeIri: Option[SmartIri], subjectIsResource: Boolean) = typeInspectionResult.getTypeOfEntity(statementPattern.subj) match {
case Some(NonPropertyTypeInfo(subjectTypeIri, isResourceType, _)) => (Some(subjectTypeIri), isResourceType)
case _ => (None, false)
}
// Is the subject of the statement a resource?
if (subjectIsResource) {
// Yes. Is the object of the statement also a resource?
if (propertyTypeInfo.objectIsResourceType) {
// Yes. This is a link property. Make sure that the object is either an IRI or a variable (cannot be a literal).
statementPattern.obj match {
case _: IriRef => ()
case objectVar: QueryVariable => checkSubjectInOrderBy(objectVar)
case other => throw GravsearchException(s"Object of a linking statement must be an IRI or a variable, but ${other.toSparql} given.")
}
// Generate statement patterns to match the link value.
val linkValueStatements = generateStatementsForLinkValue(
linkSource = statementPattern.subj,
linkPred = statementPattern.pred,
linkTarget = statementPattern.obj
)
// Add the input statement, which uses the link property, to the generated statements about the link value.
statementPatternToInternalSchema(statementPattern, typeInspectionResult) +: linkValueStatements
} else {
// The subject is a resource, but the object isn't, so this isn't a link property.
// Is the property a resource metadata property?
statementPattern.pred match {
case iriRef: IriRef if OntologyConstants.ResourceMetadataPropertyAxioms.contains(iriRef.iri.toString) =>
// Yes. Store the variable if provided.
val maybeObjectVar: Option[QueryVariable] = statementPattern.obj match {
case queryVar: QueryVariable =>
checkSubjectInOrderBy(queryVar)
Some(queryVar)
case _ => None
}
resourceMetadataVariables ++= maybeObjectVar
// Just convert the statement pattern to the internal schema
Seq(statementPatternToInternalSchema(statementPattern, typeInspectionResult))
case _ =>
// The property is not a resource metadata property. Make sure the object is a variable.
val objectVar: QueryVariable = statementPattern.obj match {
case queryVar: QueryVariable =>
checkSubjectInOrderBy(queryVar)
queryVar
case other => throw GravsearchException(s"Object of a value property statement must be a QueryVariable, but ${other.toSparql} given.")
}
// Does the variable refer to a Knora value object? We assume it does if the query just uses the
// simple schema. If the query uses the complex schema, check whether the property's object type is a
// Knora API v2 value class.
val objectVarIsValueObject = querySchema == ApiV2Simple ||
OntologyConstants.KnoraApiV2Complex.ValueClasses.contains(propertyTypeInfo.objectTypeIri.toString)
if (objectVarIsValueObject) {
// The variable refers to a value object.
// Convert the statement to the internal schema, and add a statement to check that the value object is not marked as deleted.
val valueObjectIsNotDeleted = StatementPattern.makeExplicit(subj = statementPattern.obj, pred = IriRef(OntologyConstants.KnoraBase.IsDeleted.toSmartIri), obj = XsdLiteral(value = "false", datatype = OntologyConstants.Xsd.Boolean.toSmartIri))
// check if the object var is used as a sort criterion
val objectVarAsSortCriterionMaybe = inputOrderBy.find(criterion => criterion.queryVariable == objectVar)
val orderByStatement: Option[QueryPattern] = if (objectVarAsSortCriterionMaybe.nonEmpty) {
// it is used as a sort criterion, create an additional statement to get the literal value
val criterion = objectVarAsSortCriterionMaybe.get
val propertyIri: SmartIri = typeInspectionResult.getTypeOfEntity(criterion.queryVariable) match {
case Some(nonPropertyTypeInfo: NonPropertyTypeInfo) =>
valueTypesToValuePredsForOrderBy.getOrElse(nonPropertyTypeInfo.typeIri.toString, throw GravsearchException(s"${criterion.queryVariable.toSparql} cannot be used in ORDER BY")).toSmartIri
case Some(_) => throw GravsearchException(s"Variable ${criterion.queryVariable.toSparql} represents a property, and therefore cannot be used in ORDER BY")
case None => throw GravsearchException(s"No type information found for ${criterion.queryVariable.toSparql}")
}
// Generate the variable name.
val variableForLiteral: QueryVariable = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(criterion.queryVariable, propertyIri.toString)
// put the generated variable into a collection so it can be reused in `NonTriplestoreSpecificGravsearchToPrequeryGenerator.getOrderBy`
// set to true when the variable already exists
val variableForLiteralExists = !addGeneratedVariableForValueLiteral(criterion.queryVariable, variableForLiteral)
if (!variableForLiteralExists) {
// Generate a statement to get the literal value
val statementPatternForSortCriterion = StatementPattern.makeExplicit(subj = criterion.queryVariable, pred = IriRef(propertyIri), obj = variableForLiteral)
Some(statementPatternForSortCriterion)
} else {
// statement has already been created
None
}
} else {
// it is not a sort criterion
None
}
Seq(statementPatternToInternalSchema(statementPattern, typeInspectionResult), valueObjectIsNotDeleted) ++ orderByStatement
} else {
// The variable doesn't refer to a value object. Just convert the statement pattern to the internal schema.
Seq(statementPatternToInternalSchema(statementPattern, typeInspectionResult))
}
}
}
} else {
// The subject isn't a resource, so it must be a value object or standoff node. Is the query in the complex schema?
if (querySchema == ApiV2Complex) {
// Yes. If the subject is a standoff tag and the object is a resource, that's an error, because the client
// has to use the knora-api:standoffLink function instead.
if (maybeSubjectTypeIri.contains(OntologyConstants.KnoraApiV2Complex.StandoffTag.toSmartIri) && propertyTypeInfo.objectIsResourceType) {
throw GravsearchException(s"Invalid statement pattern (use the knora-api:standoffLink function instead): ${statementPattern.toSparql.trim}")
} else {
// Is the object of the statement a list node?
propertyTypeInfo.objectTypeIri match {
case SmartIri(OntologyConstants.KnoraApiV2Complex.ListNode) =>
// Yes, transform statement so it also matches any of the subnodes of the given node
handleListNode()
case _ =>
// No, just convert the statement pattern to the internal schema.
Seq(statementPatternToInternalSchema(statementPattern, typeInspectionResult))
}
}
} else {
// The query is in the simple schema, so the statement is invalid.
throw GravsearchException(s"Invalid statement pattern: ${statementPattern.toSparql.trim}")
}
}
}
/**
* Processes Gravsearch options.
*
* @param statementPattern the statement specifying the option to be set.
*/
private def processGravsearchOption(statementPattern: StatementPattern): Unit = {
statementPattern.pred match {
case iriRef: IriRef if OntologyConstants.KnoraApi.UseInferenceIris.contains(iriRef.iri.toString) =>
useInference = statementPattern.obj match {
case xsdLiteral: XsdLiteral if xsdLiteral.datatype.toString == OntologyConstants.Xsd.Boolean =>
xsdLiteral.value.toBoolean
case other => throw GravsearchException(s"Invalid object for knora-api:useInference: ${other.toSparql}")
}
case other => throw GravsearchException(s"Invalid predicate for knora-api:GravsearchOptions: ${other.toSparql}")
}
}
protected def processStatementPatternFromWhereClause(statementPattern: StatementPattern, inputOrderBy: Seq[OrderCriterion]): Seq[QueryPattern] = {
// Does this statement set a Gravsearch option?
statementPattern.subj match {
case iriRef: IriRef if OntologyConstants.KnoraApi.GravsearchOptionsIris.contains(iriRef.iri.toString) =>
// Yes. Process the option.
processGravsearchOption(statementPattern)
Seq.empty[QueryPattern]
case _ =>
// No. look at the statement's subject, predicate, and object and generate additional statements if needed based on the given type information.
// transform the originally given statement if necessary when processing the predicate
// check if there exists type information for the given statement's subject
val additionalStatementsForSubj: Seq[QueryPattern] = checkForNonPropertyTypeInfoForEntity(
entity = statementPattern.subj,
typeInspectionResult = typeInspectionResult,
processedTypeInfo = processedTypeInformationKeysWhereClause,
conversionFuncForNonPropertyType = createAdditionalStatementsForNonPropertyType
)
// check if there exists type information for the given statement's object
val additionalStatementsForObj: Seq[QueryPattern] = checkForNonPropertyTypeInfoForEntity(
entity = statementPattern.obj,
typeInspectionResult = typeInspectionResult,
processedTypeInfo = processedTypeInformationKeysWhereClause,
conversionFuncForNonPropertyType = createAdditionalStatementsForNonPropertyType
)
// Add additional statements based on the whole input statement, e.g. to deal with the value object or the link value, and transform the original statement.
val additionalStatementsForWholeStatement: Seq[QueryPattern] = checkForPropertyTypeInfoForStatement(
statementPattern = statementPattern,
typeInspectionResult = typeInspectionResult,
conversionFuncForPropertyType = convertStatementForPropertyType(inputOrderBy)
)
additionalStatementsForSubj ++ additionalStatementsForWholeStatement ++ additionalStatementsForObj
}
}
/**
* Creates additional statements for a given [[Entity]] based on type information using `conversionFuncForNonPropertyType`
* for a non property type (e.g., a resource).
*
* @param entity the entity to be taken into consideration (a statement's subject or object).
* @param typeInspectionResult type information.
* @param processedTypeInfo the keys of type information that have already been looked at.
* @param conversionFuncForNonPropertyType the function to use to create additional statements.
* @return a sequence of [[QueryPattern]] representing the additional statements.
*/
private def checkForNonPropertyTypeInfoForEntity(entity: Entity, typeInspectionResult: GravsearchTypeInspectionResult, processedTypeInfo: mutable.Set[TypeableEntity], conversionFuncForNonPropertyType: (NonPropertyTypeInfo, Entity) => Seq[QueryPattern]): Seq[QueryPattern] = {
val typesNotYetProcessed = typeInspectionResult.copy(entities = typeInspectionResult.entities -- processedTypeInfo)
typesNotYetProcessed.getTypeOfEntity(entity) match {
case Some(nonPropInfo: NonPropertyTypeInfo) =>
// add a TypeableEntity for subject to prevent duplicates
processedTypeInfo += GravsearchTypeInspectionUtil.toTypeableEntity(entity)
conversionFuncForNonPropertyType(nonPropInfo, entity)
case Some(other) => throw AssertionException(s"NonPropertyTypeInfo expected for $entity, got $other")
case None => Seq.empty[QueryPattern]
}
}
/**
* Converts the given statement based on the given type information using `conversionFuncForPropertyType`.
*
* @param statementPattern the statement to be converted.
* @param typeInspectionResult type information.
* @param conversionFuncForPropertyType the function to use for the conversion.
* @return a sequence of [[QueryPattern]] representing the converted statement.
*/
private def checkForPropertyTypeInfoForStatement(statementPattern: StatementPattern, typeInspectionResult: GravsearchTypeInspectionResult, conversionFuncForPropertyType: (PropertyTypeInfo, StatementPattern, GravsearchTypeInspectionResult) => Seq[QueryPattern]): Seq[QueryPattern] = {
typeInspectionResult.getTypeOfEntity(statementPattern.pred) match {
case Some(propInfo: PropertyTypeInfo) =>
// process type information for the predicate into additional statements
conversionFuncForPropertyType(propInfo, statementPattern, typeInspectionResult)
case Some(other) => throw AssertionException(s"PropertyTypeInfo expected for ${statementPattern.pred}, got $other")
case None =>
// no type information given and thus no further processing needed, just return the originally given statement (e.g., rdf:type), converted to the internal schema.
Seq(statementPatternToInternalSchema(statementPattern, typeInspectionResult))
}
}
// A Map of knora-api value types (both complex and simple) to the corresponding knora-base value predicates
// that point to literals. This is used only for generating additional statements for ORDER BY clauses, so it only needs to include
// types that have a meaningful order.
private val valueTypesToValuePredsForOrderBy: Map[IRI, IRI] = Map(
OntologyConstants.Xsd.Integer -> OntologyConstants.KnoraBase.ValueHasInteger,
OntologyConstants.Xsd.Decimal -> OntologyConstants.KnoraBase.ValueHasDecimal,
OntologyConstants.Xsd.Boolean -> OntologyConstants.KnoraBase.ValueHasBoolean,
OntologyConstants.Xsd.String -> OntologyConstants.KnoraBase.ValueHasString,
OntologyConstants.KnoraApiV2Simple.Date -> OntologyConstants.KnoraBase.ValueHasStartJDN,
OntologyConstants.KnoraApiV2Simple.Color -> OntologyConstants.KnoraBase.ValueHasColor,
OntologyConstants.KnoraApiV2Simple.Geoname -> OntologyConstants.KnoraBase.ValueHasGeonameCode,
OntologyConstants.KnoraApiV2Complex.TextValue -> OntologyConstants.KnoraBase.ValueHasString,
OntologyConstants.KnoraApiV2Complex.IntValue -> OntologyConstants.KnoraBase.ValueHasInteger,
OntologyConstants.KnoraApiV2Complex.DecimalValue -> OntologyConstants.KnoraBase.ValueHasDecimal,
OntologyConstants.KnoraApiV2Complex.TimeValue -> OntologyConstants.KnoraBase.ValueHasTimeStamp,
OntologyConstants.KnoraApiV2Complex.BooleanValue -> OntologyConstants.KnoraBase.ValueHasBoolean,
OntologyConstants.KnoraApiV2Complex.DateValue -> OntologyConstants.KnoraBase.ValueHasStartJDN,
OntologyConstants.KnoraApiV2Complex.ColorValue -> OntologyConstants.KnoraBase.ValueHasColor,
OntologyConstants.KnoraApiV2Complex.GeonameValue -> OntologyConstants.KnoraBase.ValueHasGeonameCode
)
/**
* Calls [[GravsearchQueryChecker.checkStatement]], then converts the specified statement pattern to the internal schema.
*
* @param statementPattern the statement pattern to be converted.
* @param typeInspectionResult the type inspection result.
* @return the converted statement pattern.
*/
private def statementPatternToInternalSchema(statementPattern: StatementPattern, typeInspectionResult: GravsearchTypeInspectionResult): StatementPattern = {
GravsearchQueryChecker.checkStatement(
statementPattern = statementPattern,
querySchema = querySchema,
typeInspectionResult = typeInspectionResult
)
statementPattern.toOntologySchema(InternalSchema)
}
/**
* Given a variable representing a linking property, creates a variable representing the corresponding link value property.
*
* @param linkingPropertyQueryVariable variable representing a linking property.
* @return variable representing the corresponding link value property.
*/
private def createLinkValuePropertyVariableFromLinkingPropertyVariable(linkingPropertyQueryVariable: QueryVariable): QueryVariable = {
SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(
base = linkingPropertyQueryVariable,
propertyIri = OntologyConstants.KnoraBase.HasLinkToValue
)
}
/**
* Represents a transformed Filter expression and additional statement patterns that possibly had to be created during transformation.
*
* @param expression the transformed FILTER expression. In some cases, a given FILTER expression is replaced by additional statements, but
* only if it is the top-level expression in the FILTER.
* @param additionalPatterns additionally created query patterns.
*/
protected case class TransformedFilterPattern(expression: Option[Expression], additionalPatterns: Seq[QueryPattern] = Seq.empty[QueryPattern])
/**
* Handles query variables that represent properties in a [[FilterPattern]].
*
* @param queryVar the query variable to be handled.
* @param comparisonOperator the comparison operator used in the filter pattern.
* @param iriRef the IRI the property query variable is restricted to.
* @param propInfo information about the query variable's type.
* @return a [[TransformedFilterPattern]].
*/
private def handlePropertyIriQueryVar(queryVar: QueryVariable, comparisonOperator: CompareExpressionOperator.Value, iriRef: IriRef, propInfo: PropertyTypeInfo): TransformedFilterPattern = {
iriRef.iri.checkApiV2Schema(querySchema, throw GravsearchException(s"Invalid schema for IRI: ${iriRef.toSparql}"))
// make sure that the comparison operator is a CompareExpressionOperator.EQUALS
if (comparisonOperator != CompareExpressionOperator.EQUALS)
throw GravsearchException(s"Comparison operator in a CompareExpression for a property type must be ${CompareExpressionOperator.EQUALS}, but '$comparisonOperator' given (for negations use 'FILTER NOT EXISTS')")
TransformedFilterPattern(Some(CompareExpression(queryVar, comparisonOperator, iriRef.toOntologySchema(InternalSchema))))
}
/**
* Handles query variables that represent a list node label in a [[FilterPattern]].
*
* @param queryVar the query variable to be handled.
* @param comparisonOperator the comparison operator used in the filter pattern.
* @param literalValueExpression the label to match against.
*/
private def handleListQueryVar(queryVar: QueryVariable, comparisonOperator: CompareExpressionOperator.Value, literalValueExpression: Expression): TransformedFilterPattern = {
// make sure that the expression is a literal of the expected type
val nodeLabel: String = literalValueExpression match {
case xsdLiteral: XsdLiteral if xsdLiteral.datatype.toString == OntologyConstants.KnoraApiV2Simple.ListNode => xsdLiteral.value
case other => throw GravsearchException(s"Invalid type for literal ${OntologyConstants.KnoraApiV2Simple.ListNode}")
}
val validComparisonOperators = Set(CompareExpressionOperator.EQUALS)
// check if comparison operator is supported
if (!validComparisonOperators.contains(comparisonOperator))
throw GravsearchException(s"Invalid operator '$comparisonOperator' in expression (allowed operators in this context are ${validComparisonOperators.map(op => "'" + op + "'").mkString(", ")})")
// Generate a variable name representing the list node pointed to by the list value object
val listNodeVar: QueryVariable = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(
base = queryVar,
propertyIri = OntologyConstants.KnoraBase.ValueHasListNode
)
// Generate variable name representing the label of the list node pointed to
val listNodeLabel: QueryVariable = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(
base = queryVar,
propertyIri = OntologyConstants.Rdfs.Label
)
TransformedFilterPattern(
// use the SPARQL-STR function because the list node label has a language tag
Some(CompareExpression(StrFunction(listNodeLabel), comparisonOperator, XsdLiteral(nodeLabel, OntologyConstants.Xsd.String.toSmartIri))), // compares the provided literal to the value object's literal value
Seq(
// connects the query variable with the list node label
StatementPattern.makeExplicit(subj = queryVar, pred = IriRef(OntologyConstants.KnoraBase.ValueHasListNode.toSmartIri), listNodeVar),
StatementPattern.makeExplicit(subj = listNodeVar, pred = IriRef(OntologyConstants.Rdfs.Label.toSmartIri), obj = listNodeLabel)
)
)
}
/**
* Handles query variables that represent literals in a [[FilterPattern]].
*
* @param queryVar the query variable to be handled.
* @param comparisonOperator the comparison operator used in the filter pattern.
* @param literalValueExpression the literal provided in the [[FilterPattern]] as an [[Expression]].
* @param xsdType valid xsd types of the literal.
* @param valueHasProperty the property of the value object pointing to the literal (in the internal schema).
* @param validComparisonOperators a set of valid comparison operators, if to be restricted.
* @return a [[TransformedFilterPattern]].
*/
private def handleLiteralQueryVar(queryVar: QueryVariable, comparisonOperator: CompareExpressionOperator.Value, literalValueExpression: Expression, xsdType: Set[IRI], valueHasProperty: IRI, validComparisonOperators: Set[CompareExpressionOperator.Value] = Set.empty[CompareExpressionOperator.Value]): TransformedFilterPattern = {
// make sure that the expression is a literal of the expected type
val literal: XsdLiteral = literalValueExpression match {
case xsdLiteral: XsdLiteral if xsdType(xsdLiteral.datatype.toString) => xsdLiteral
case other => throw GravsearchException(s"Invalid right argument ${other.toSparql} in comparison (allowed types in this context are ${xsdType.map(_.toSmartIri.toSparql).mkString(", ")})")
}
// check if comparison operator is supported for given type
if (validComparisonOperators.nonEmpty && !validComparisonOperators(comparisonOperator))
throw GravsearchException(s"Invalid operator '$comparisonOperator' in expression (allowed operators in this context are ${validComparisonOperators.map(op => "'" + op + "'").mkString(", ")})")
// Does the variable refer to resource metadata?
if (resourceMetadataVariables.contains(queryVar)) {
// Yes. Leave the expression as is.
TransformedFilterPattern(
Some(CompareExpression(queryVar, comparisonOperator, literal)),
Seq.empty
)
} else {
// The variable does not refer to resource metadata.
// Generate a variable name representing the literal attached to the value object.
val valueObjectLiteralVar: QueryVariable = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(
base = queryVar,
propertyIri = valueHasProperty
)
// Add a statement to assign the literal to a variable, which we'll use in the transformed FILTER expression,
// if that statement hasn't been added already.
val statementToAddForValueHas: Seq[StatementPattern] = if (addGeneratedVariableForValueLiteral(queryVar, valueObjectLiteralVar)) {
Seq(
// connects the query variable with the value object (internal structure: values are represented as objects)
StatementPattern.makeExplicit(subj = queryVar, pred = IriRef(valueHasProperty.toSmartIri), valueObjectLiteralVar)
)
} else {
Seq.empty[StatementPattern]
}
TransformedFilterPattern(
Some(CompareExpression(valueObjectLiteralVar, comparisonOperator, literal)), // compares the provided literal to the value object's literal value
statementToAddForValueHas
)
}
}
/**
* Handles query variables that represent a date in a [[FilterPattern]].
*
* @param queryVar the query variable to be handled.
* @param comparisonOperator the comparison operator used in the filter pattern.
* @param dateValueExpression the date literal provided in the [[FilterPattern]] as an [[Expression]].
* @return a [[TransformedFilterPattern]].
*/
private def handleDateQueryVar(queryVar: QueryVariable, comparisonOperator: CompareExpressionOperator.Value, dateValueExpression: Expression): TransformedFilterPattern = {
// make sure that the right argument is a string literal (dates are represented as knora date strings in knora-api simple)
val dateStringLiteral: XsdLiteral = dateValueExpression match {
case dateStrLiteral: XsdLiteral if dateStrLiteral.datatype.toString == OntologyConstants.KnoraApiV2Simple.Date => dateStrLiteral
case other => throw GravsearchException(s"Invalid right argument ${other.toSparql} in date comparison")
}
// validate Knora date string
val dateStr: String = stringFormatter.validateDate(dateStringLiteral.value, throw BadRequestException(s"${dateStringLiteral.value} is not a valid date string"))
// Convert it to Julian Day Numbers.
val dateValueContent = DateValueContentV2.parse(dateStr)
// Generate a variable name representing the period's start
val dateValueHasStartVar = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(base = queryVar, propertyIri = OntologyConstants.KnoraBase.ValueHasStartJDN)
// sort dates by their period's start (in the prequery)
// is set to `true` if the date value object var is a sort criterion and has been handled already
val dateValVarExists: Boolean = !addGeneratedVariableForValueLiteral(queryVar, dateValueHasStartVar)
// Generate a variable name representing the period's end
val dateValueHasEndVar = SparqlTransformer.createUniqueVariableNameFromEntityAndProperty(base = queryVar, propertyIri = OntologyConstants.KnoraBase.ValueHasEndJDN)
// connects the value object with the periods start variable
// only generate a new statement if it has not already been created when handling the sort criteria
val dateValStartStatementOption: Option[StatementPattern] = if (!dateValVarExists) {
Some(StatementPattern.makeExplicit(subj = queryVar, pred = IriRef(OntologyConstants.KnoraBase.ValueHasStartJDN.toSmartIri), obj = dateValueHasStartVar))
} else {
None
}
// connects the value object with the periods end variable
val dateValEndStatement = StatementPattern.makeExplicit(subj = queryVar, pred = IriRef(OntologyConstants.KnoraBase.ValueHasEndJDN.toSmartIri), obj = dateValueHasEndVar)
// process filter expression based on given comparison operator
comparisonOperator match {
case CompareExpressionOperator.EQUALS =>
// any overlap in considered as equality
val leftArgFilter = CompareExpression(XsdLiteral(dateValueContent.valueHasStartJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri), CompareExpressionOperator.LESS_THAN_OR_EQUAL_TO, dateValueHasEndVar)
val rightArgFilter = CompareExpression(XsdLiteral(dateValueContent.valueHasEndJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri), CompareExpressionOperator.GREATER_THAN_OR_EQUAL_TO, dateValueHasStartVar)
val filter = AndExpression(leftArgFilter, rightArgFilter)
val statementsToAdd = (dateValStartStatementOption.toSeq :+ dateValEndStatement).filterNot(statement => generatedDateStatements.contains(statement))
generatedDateStatements ++= statementsToAdd
TransformedFilterPattern(
Some(filter),
statementsToAdd
)
case CompareExpressionOperator.NOT_EQUALS =>
// no overlap in considered as inequality (negation of equality)
val leftArgFilter = CompareExpression(XsdLiteral(dateValueContent.valueHasStartJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri), CompareExpressionOperator.GREATER_THAN, dateValueHasEndVar)
val rightArgFilter = CompareExpression(XsdLiteral(dateValueContent.valueHasEndJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri), CompareExpressionOperator.LESS_THAN, dateValueHasStartVar)
val filter = OrExpression(leftArgFilter, rightArgFilter)
val statementsToAdd = (dateValStartStatementOption.toSeq :+ dateValEndStatement).filterNot(statement => generatedDateStatements.contains(statement))
generatedDateStatements ++= statementsToAdd
TransformedFilterPattern(
Some(filter),
statementsToAdd
)
case CompareExpressionOperator.LESS_THAN =>
// period ends before indicated period
val filter = CompareExpression(dateValueHasEndVar, CompareExpressionOperator.LESS_THAN, XsdLiteral(dateValueContent.valueHasStartJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri))
val statementsToAdd = (dateValStartStatementOption.toSeq :+ dateValEndStatement).filterNot(statement => generatedDateStatements.contains(statement)) // dateValStartStatement may be used as ORDER BY statement
generatedDateStatements ++= statementsToAdd
TransformedFilterPattern(
Some(filter),
statementsToAdd
)
case CompareExpressionOperator.LESS_THAN_OR_EQUAL_TO =>
// period ends before indicated period or equals it (any overlap)
val filter = CompareExpression(dateValueHasStartVar, CompareExpressionOperator.LESS_THAN_OR_EQUAL_TO, XsdLiteral(dateValueContent.valueHasEndJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri))
val statementToAdd = if (dateValStartStatementOption.nonEmpty && !generatedDateStatements.contains(dateValStartStatementOption.get)) {
generatedDateStatements += dateValStartStatementOption.get
Seq(dateValStartStatementOption.get)
} else {
Seq.empty[StatementPattern]
}
TransformedFilterPattern(
Some(filter),
statementToAdd
)
case CompareExpressionOperator.GREATER_THAN =>
// period starts after end of indicated period
val filter = CompareExpression(dateValueHasStartVar, CompareExpressionOperator.GREATER_THAN, XsdLiteral(dateValueContent.valueHasEndJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri))
val statementToAdd = if (dateValStartStatementOption.nonEmpty && !generatedDateStatements.contains(dateValStartStatementOption.get)) {
generatedDateStatements += dateValStartStatementOption.get
Seq(dateValStartStatementOption.get)
} else {
Seq.empty[StatementPattern]
}
TransformedFilterPattern(
Some(filter),
statementToAdd
)
case CompareExpressionOperator.GREATER_THAN_OR_EQUAL_TO =>
// period starts after indicated period or equals it (any overlap)
val filter = CompareExpression(dateValueHasEndVar, CompareExpressionOperator.GREATER_THAN_OR_EQUAL_TO, XsdLiteral(dateValueContent.valueHasStartJDN.toString, OntologyConstants.Xsd.Integer.toSmartIri))
val statementsToAdd = (dateValStartStatementOption.toSeq :+ dateValEndStatement).filterNot(statement => generatedDateStatements.contains(statement)) // dateValStartStatement may be used as ORDER BY statement
generatedDateStatements ++= statementsToAdd
TransformedFilterPattern(
Some(filter),
statementsToAdd
)
case other => throw GravsearchException(s"Invalid operator '$other' in date comparison")
}
}
/**
* Handles a [[FilterPattern]] containing a query variable.
*
* @param queryVar the query variable.
* @param compareExpression the filter pattern's compare expression.
* @param typeInspectionResult the type inspection results.
* @return a [[TransformedFilterPattern]].
*/
private def handleQueryVar(queryVar: QueryVariable, compareExpression: CompareExpression, typeInspectionResult: GravsearchTypeInspectionResult): TransformedFilterPattern = {
typeInspectionResult.getTypeOfEntity(queryVar) match {
case Some(typeInfo) =>
// check if queryVar represents a property or a value
typeInfo match {
case propInfo: PropertyTypeInfo =>
// left arg queryVar is a variable representing a property
// therefore the right argument must be an IRI restricting the property variable to a certain property
compareExpression.rightArg match {
case iriRef: IriRef =>
handlePropertyIriQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
iriRef = iriRef,
propInfo = propInfo
)
case other => throw GravsearchException(s"Invalid right argument ${other.toSparql} in comparison (expected a property IRI)")
}
case nonPropInfo: NonPropertyTypeInfo =>
// Is the query using the API v2 simple schema?
if (querySchema == ApiV2Simple) {
// Yes. Depending on the value type, transform the given Filter pattern.
// Add an extra level by getting the value literal from the value object.
// If queryVar refers to a value object as a literal, for the value literal an extra variable has to be created, taking its type into account.
nonPropInfo.typeIri.toString match {
case OntologyConstants.Xsd.Integer =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.Integer),
valueHasProperty = OntologyConstants.KnoraBase.ValueHasInteger
)
case OntologyConstants.Xsd.Decimal =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.Decimal, OntologyConstants.Xsd.Integer), // an integer literal is also valid
valueHasProperty = OntologyConstants.KnoraBase.ValueHasDecimal
)
case OntologyConstants.Xsd.DateTimeStamp =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.DateTimeStamp),
valueHasProperty = OntologyConstants.KnoraBase.ValueHasTimeStamp
)
case OntologyConstants.Xsd.Boolean =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.Boolean),
valueHasProperty = OntologyConstants.KnoraBase.ValueHasBoolean,
validComparisonOperators = Set(CompareExpressionOperator.EQUALS, CompareExpressionOperator.NOT_EQUALS)
)
case OntologyConstants.Xsd.String =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.String),
valueHasProperty = OntologyConstants.KnoraBase.ValueHasString,
validComparisonOperators = Set(CompareExpressionOperator.EQUALS, CompareExpressionOperator.NOT_EQUALS)
)
case OntologyConstants.Xsd.Uri =>
handleLiteralQueryVar(
queryVar = queryVar,
comparisonOperator = compareExpression.operator,
literalValueExpression = compareExpression.rightArg,
xsdType = Set(OntologyConstants.Xsd.Uri),
valueHasProperty = OntologyConstants.KnoraBase.ValueHasUri,
validComparisonOperators = Set(CompareExpressionOperator.EQUALS, CompareExpressionOperator.NOT_EQUALS)
)
case OntologyConstants.KnoraApiV2Simple.Date =>
handleDateQueryVar(queryVar = queryVar, comparisonOperator = compareExpression.operator, dateValueExpression = compareExpression.rightArg)
case OntologyConstants.KnoraApiV2Simple.ListNode =>
handleListQueryVar(queryVar = queryVar, comparisonOperator = compareExpression.operator, literalValueExpression = compareExpression.rightArg)
case other => throw NotImplementedException(s"Value type $other not supported in FilterExpression")
}
} else {
// The query is using the complex schema. Keep the expression as it is.
TransformedFilterPattern(Some(compareExpression))
}
}
case None =>
throw GravsearchException(s"No type information found about ${queryVar.toSparql}")
}
}
/**
*
* Handles the use of the SPARQL lang function in a [[FilterPattern]].
*
* @param langFunctionCall the lang function call to be handled.
* @param compareExpression the filter pattern's compare expression.
* @param typeInspectionResult the type inspection results.
* @return a [[TransformedFilterPattern]].
*/
private def handleLangFunctionCall(langFunctionCall: LangFunction, compareExpression: CompareExpression, typeInspectionResult: GravsearchTypeInspectionResult): TransformedFilterPattern = {
if (querySchema == ApiV2Complex) {
throw GravsearchException(s"The lang function is not allowed in a Gravsearch query that uses the API v2 complex schema")
}