Search code examples
javaprologtuprolog

How to make TuProlog recognize invalid facts?


I have following two Prolog files:

ontology.pl:

isSite(Url) :- string(Url).
guestPostPublished(GuestPostId, Date, Site, Url) :-
 string(GuestPostId),
 date(Date),
 isSite(Site),
 string(Url),
 \+(guestPostPublished(GuestPostId, _, _, _)).

invalidFile.pl:

isSite('somesite.com').
guestPostPublished(
    'gp1',
    date(2016,2,2),
    'somesite.com',
    'someUrl').

guestPostPublished(
    'gp1',
    date(2016,2,2),
    'somesite.com',
    'anotherUrl').

invalidFile.pl is invalid because it violates the rule specified in ontology.pl that all GuestPostIds must be unique.

When I load that data into my engine, I except it to throw some exception indicating that the data are invalid. But it doesn't.

What am I doing wrong? How can I make sure that when I feed invalid data to TuProlog engine, I get notification of some sort (e. g. an exception or some flag) ?

Here's the relevant fragment of my code (you can find the entire code here):

@Test
public void test2() throws InvalidObjectIdException, IOException,
        MalformedGoalException, InvalidTheoryException, UnknownVarException, NoSolutionException,
        NoMoreSolutionException, InvalidLibraryException {
    final Prolog engine = createEngine();

    try
    {
        loadPrologFiles(engine, new String[]{
                "src/main/resources/ontology.pl",
                "src/main/resources/invalidFile.pl"
        });
        Assert.fail("Engine swallows invalid Prolog file.");
    }
    catch (final Exception exception) {
        // TODO: Check that the right exception is thrown
    }
    final List<String> result = getResults(engine, "guestPostPublished(_,X,_,_).", "X");
    System.out.println("result: " + result);
}

private Prolog createEngine() throws InvalidObjectIdException {
    final Prolog engine = new Prolog();
    engine.addOutputListener(new OutputListener() {
        public void onOutput(OutputEvent outputEvent) {
            System.out.println(String.format("PROLOG: %s", outputEvent.getMsg()));
        }
    });
    Library lib = engine.getLibrary("alice.tuprolog.lib.OOLibrary");
    ((OOLibrary)lib).register(new Struct("stdout"), System.out);
    return engine;
}

private void loadPrologFiles(final Prolog engine, final String[] files) throws IOException, InvalidTheoryException {
    final List<String> paths = Arrays.asList(files);
    final StringBuilder theoryBuilder = new StringBuilder();

    for (final String path : paths) {
        theoryBuilder.append(System.lineSeparator());
        theoryBuilder.append("% ");
        theoryBuilder.append(path);
        theoryBuilder.append(" (START)");
        theoryBuilder.append(System.lineSeparator());
        theoryBuilder.append(FileUtils.readFileToString(new File(path)));
        theoryBuilder.append(System.lineSeparator());
        theoryBuilder.append("% ");
        theoryBuilder.append(path);
        theoryBuilder.append(" (END)");
        theoryBuilder.append(System.lineSeparator());
    }

    final Theory test1 = new Theory(theoryBuilder.toString());
    engine.setTheory(test1);
}

private List<String> getResults(final Prolog engine, final String query, final String varName) throws
        MalformedGoalException, NoSolutionException, UnknownVarException, NoMoreSolutionException {
    SolveInfo res2 = engine.solve(query);

    final List<String> result = new LinkedList<String>();
    if (res2.isSuccess()) {
        result.add(res2.getTerm(varName).toString());
        while (engine.hasOpenAlternatives()) {
            res2 = engine.solveNext();
            final Term x2 = res2.getTerm("X");
            result.add(x2.toString());
        }
    }
    return result;
}

Solution

  • To set data integrity constraints on a Prolog table of facts, you need to approach this differently. I would suggest you first try to do it in pure Prolog, without the Java bits, just to get some understanding of what is going on.

    If the database is static and does not change, it is easy: just load it, then run queries against it that do the data integrity checks. For example, you have a table site/1 with a single column, and you want to make sure that all values are strings:

    There is no site(S) so that S is not a string

    \+ ( site(S), \+ string(S) )
    

    If you want to wrap this into a predicate, you must name the predicate with a different name than your table!

    site_must_be_string :-
        \+ ( site(S), \+ string(S) ).
    

    Or, for the other one, a unique column (primary key):

    There are no duplicates among the first arguments to guest_post_published/4

    findall(ID, guest_post_published(ID, _, _, _), IDs),
    length(IDs, Len),
    sort(IDs, Sorted),   % sort/2 removes duplicates!
    length(Sorted, Len). % length does not change after sorting
    

    You probably need to wrap this up in a predicate of its own, too.