diff --git a/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/functions/Log.java b/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/functions/Log.java new file mode 100644 index 00000000..9ff03c36 --- /dev/null +++ b/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/functions/Log.java @@ -0,0 +1,63 @@ +package com.the_qa_company.qendpoint.functions; + +import com.the_qa_company.qendpoint.store.EndpointStore; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.base.CoreDatatype; +import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException; +import org.eclipse.rdf4j.query.algebra.evaluation.function.Function; + +public class Log implements Function { + @Override + public String getURI() { + return EndpointStore.BASE_URI + "log"; + } + + @Override + public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException { + if (args.length < 1 || args.length > 2) { + throw new ValueExprEvaluationException(getURI() + "(value, base = 10)"); + } + + Value valueVal = args[0]; + + if (!valueVal.isLiteral() || !(valueVal instanceof Literal lit)) { + throw new ValueExprEvaluationException(getURI() + " : value should be a literal"); + } + CoreDatatype cdt = lit.getCoreDatatype(); + + if (!cdt.isXSDDatatype() || !cdt.asXSDDatatype().orElseThrow().isNumericDatatype()) { + throw new ValueExprEvaluationException(getURI() + " : value should be a numeric literal"); + } + + double val = lit.doubleValue(); + + if (val <= 0) { + throw new ValueExprEvaluationException(getURI() + " : value should be a positive number"); + } + + int base; + + if (args.length >= 2) { + Value baseVal = args[1]; + + if (!baseVal.isLiteral() || !(baseVal instanceof Literal litb)) { + throw new ValueExprEvaluationException(getURI() + " : base should be a literal"); + } + CoreDatatype bcdt = litb.getCoreDatatype(); + + if (!bcdt.isXSDDatatype() || !bcdt.asXSDDatatype().orElseThrow().isIntegerDatatype()) { + throw new ValueExprEvaluationException(getURI() + " : base should be an int literal"); + } + + base = litb.intValue(); + } else { + base = 10; + } + if (base == 10) { + return valueFactory.createLiteral(Math.log10(val)); + } + return valueFactory.createLiteral(Math.log(val) / Math.log(base)); + } +} diff --git a/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/store/EndpointStore.java b/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/store/EndpointStore.java index 5e9a0e9c..fcc11f0d 100644 --- a/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/store/EndpointStore.java +++ b/qendpoint-store/src/main/java/com/the_qa_company/qendpoint/store/EndpointStore.java @@ -18,7 +18,6 @@ import com.the_qa_company.qendpoint.model.HDTValue; import com.the_qa_company.qendpoint.utils.BitArrayDisk; import com.the_qa_company.qendpoint.utils.CloseSafeHDT; -import com.the_qa_company.qendpoint.utils.OverrideHDTOptions; import org.apache.commons.io.FileUtils; import org.apache.commons.io.file.PathUtils; import org.eclipse.rdf4j.common.concurrent.locks.Lock; @@ -66,6 +65,10 @@ import java.util.concurrent.atomic.AtomicReference; public class EndpointStore extends AbstractNotifyingSail { + /** + * base uri + */ + public static final String BASE_URI = "http://the-qa-company.com/qendpoint/#"; /** * disable the optimizer */ diff --git a/qendpoint-store/src/main/resources/META-INF/services/org.eclipse.rdf4j.query.algebra.evaluation.function.Function b/qendpoint-store/src/main/resources/META-INF/services/org.eclipse.rdf4j.query.algebra.evaluation.function.Function index ebd8d23a..191c8193 100644 --- a/qendpoint-store/src/main/resources/META-INF/services/org.eclipse.rdf4j.query.algebra.evaluation.function.Function +++ b/qendpoint-store/src/main/resources/META-INF/services/org.eclipse.rdf4j.query.algebra.evaluation.function.Function @@ -1 +1,2 @@ -com.the_qa_company.qendpoint.functions.ParseDateFunction \ No newline at end of file +com.the_qa_company.qendpoint.functions.ParseDateFunction +com.the_qa_company.qendpoint.functions.Log diff --git a/qendpoint-store/src/test/java/com/the_qa_company/qendpoint/functions/LogTest.java b/qendpoint-store/src/test/java/com/the_qa_company/qendpoint/functions/LogTest.java new file mode 100644 index 00000000..cc1b8cda --- /dev/null +++ b/qendpoint-store/src/test/java/com/the_qa_company/qendpoint/functions/LogTest.java @@ -0,0 +1,49 @@ +package com.the_qa_company.qendpoint.functions; + +import com.the_qa_company.qendpoint.store.EndpointStore; +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.query.TupleQueryResult; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.util.Repositories; +import org.eclipse.rdf4j.sail.memory.MemoryStore; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LogTest { + @Test + public void funcTest() { + MemoryStore ms = new MemoryStore(); + SailRepository repo = new SailRepository(ms); + repo.init(); + try { + Repositories.consumeNoTransaction(repo, conn -> { + try (TupleQueryResult res = conn.prepareTupleQuery(""" + PREFIX qep: <%s> + + SELECT * { + VALUES ?number { 10 80 420 10000 20000 1000000 } + BIND (qep:log(?number) AS ?logNumberDef) + BIND (qep:log(?number, 10) AS ?logNumber10) + BIND (qep:log(?number, 16) AS ?logNumber16) + BIND (qep:log(?number, 2) AS ?logNumber2) + } + """.formatted(EndpointStore.BASE_URI)).evaluate()) { + res.forEach(bind -> { + double value = ((Literal) bind.getValue("number")).doubleValue(); + + assertEquals(((Literal) bind.getValue("logNumberDef")).doubleValue(), Math.log10(value), 0.001); + assertEquals(((Literal) bind.getValue("logNumber10")).doubleValue(), Math.log10(value), 0.001); + assertEquals(((Literal) bind.getValue("logNumber16")).doubleValue(), + Math.log10(value) / Math.log10(16), 0.001); + assertEquals(((Literal) bind.getValue("logNumber2")).doubleValue(), + Math.log10(value) / Math.log10(2), 0.001); + }); + } + }); + } finally { + repo.shutDown(); + } + } + +}