1

I'm want to implement an API beetween Prolog and MongoDB and after some research the first hurdle would be connecting to MongoDB server. I know there's allready the API prolongo so I tried to understand it, but I don't. I'm a newbie in Prolog so my question is: Where is the actuall connect to the MongoDB server?

Edited: I've understood more of the code but I'm getting an error, that I can't interpret:

So here's the code of the mongo_connection.pl:

:- module(_, [
    host/1,
    port/1
]).

:- include(include/common).

%%  host(?Host) is semidet.
%
%   True if Host is the default hostname used by MongoDB.

host(localhost).

%%  port(?Port) is semidet.
%
%   True if Port is the default port used by MongoDB.

port(27017).

/** <module> Connection handling and response parsing.
 */

:- module(_, [
    new_connection/1,
    new_connection/3,
    free_connection/1,
    get_database/3,
    send_to_server/2,
    read_reply/4
]).

:- include(include/common).

%%  new_connection(-Connection) is det.
%%  new_connection(+Host, +Port, -Connection) is det.
%
%   True if Connection represents an opaque handle to a new MongoDB
%   server connection. Default values (see mongo_defaults) are used unless
%   Host and Port are provided.

new_connection(Connection) :-
    mongo_defaults:host(Host),
    mongo_defaults:port(Port),
    new_connection(Host, Port, Connection).

new_connection(Host, Port, Connection) :-
    mongo_socket:new_socket(Host, Port, Socket),
    Connection = connection(Socket).

connection_socket(Connection, Socket) :-
    mongo_util:get_nth1_arg(Connection, 1, Socket).

%%  free_connection(+Connection) is det.
%
%   Frees any resources associated with the Connection handle,
%   rendering it unusable.

free_connection(Connection) :-
    connection_socket(Connection, Socket),
    mongo_socket:free_socket(Socket).

%%  get_database(+Connection, +DatabaseName, -Database) is det.
%
%   True if Database is a handle to the database called DatabaseName
%   on Connection. No communication is performed, so the actual database
%   might or might not already exist.

get_database(Connection, DatabaseName, Database) :-
    mongo_database:new_database(Connection, DatabaseName, Database).

%%  send_to_server(+Connection, +Bytes) is det.
%
%   True if Bytes are sent over Connection.

send_to_server(Connection, Bytes) :-
    connection_socket(Connection, Socket),
    mongo_socket:send_bytes(Socket, Bytes).

%%  read_reply(+Connection, -Header, -Info, -Docs) is det.
%
%   True if Header, Info and Docs together represent the next message
%   received over Connection. Blocks until a message is completely read.
%
%   Header is the structure header(MessageLength,RequestId,ResponseTo,OpCode)
%   where:
%   - MessageLength is the total number of bytes comprising the message
%   - RequestId is the ID of this message (unused)
%   - ResponseTo is the ID of the query that triggered this response (unused)
%   - OpCode is the code signifying that this is a response (always 1)
%
%   Info is the structure info(Flags,CursorId,StartingFrom,NumberReturned)
%   where:
%   - Flags is the bitmask of flags set for this response
%   - CursorId is the cursor ID of this response
%   - StartingFrom is the query offset of the first document in Docs
%   - NumberReturned is the number of documents in Docs

read_reply(Connection, Header, Info, Docs) :-
    connection_socket(Connection, Socket),
    read_response_bytes(Socket, Bytes),
    parse_response(Bytes, Header, Info, Docs).

read_response_bytes(Socket, [B0,B1,B2,B3|Bytes]) :-
    read_message_length(Socket, [B0,B1,B2,B3], TotalLength),
    read_rest_of_message(Socket, TotalLength, Bytes).

read_message_length(Socket, Bytes, Length) :-
    mongo_socket:receive_n_bytes(Socket, 4, Bytes),
    bson_bits:integer_bytes(Length, 4, little, Bytes).

read_rest_of_message(Socket, TotalLength, Bytes) :-
    LengthRest is TotalLength - 4,
    mongo_socket:receive_n_bytes(Socket, LengthRest, Bytes).

parse_response(Bytes, Header, Info, Docs) :-
    % inspect_response_bytes(Bytes), % For debugging.
    phrase(parse_response_meta(Header, Info), Bytes, RestBytes),
    parse_response_docs(RestBytes, Docs).

parse_response_meta(Header, Info) -->
    parse_response_header(Header),
    parse_response_info(Info).

parse_response_header(Header) -->
    { Header = header(MessageLength,RequestId,ResponseTo,OpCode) },
    mongo_bytes:int32(MessageLength),
    mongo_bytes:int32(RequestId),
    mongo_bytes:int32(ResponseTo),
    mongo_bytes:int32(OpCode).

parse_response_info(Info) -->
    { Info = info(Flags,CursorId,StartingFrom,NumberReturned) },
    mongo_bytes:int32(Flags),
    mongo_bytes:int64(CursorId),
    mongo_bytes:int32(StartingFrom),
    mongo_bytes:int32(NumberReturned).

parse_response_docs(Bytes, Docs) :-
    bson:docs_bytes(Docs, Bytes).

Here is the code of mongo_socket.pl:

:- module(_, [
    new_socket/3,
    free_socket/1,
    send_bytes/2,
    receive_n_bytes/3
]).

:- include(include/common).

%%  new_socket(+Host, +Port, -Socket) is det.
%
%   True if Socket is a new TCP socket connected to Host:Port.
%
%   @throws mongo_error(Description, [SocketException])

new_socket(Host, Port, Socket) :-
    setup_call_catcher_cleanup(
        socket:tcp_socket(SocketId),
        socket:tcp_connect(SocketId, Host:Port, ReadStream, WriteStream),
        exception(SocketException),
        close_socket_and_throw(SocketId, SocketException)),
    Socket = socket(ReadStream,WriteStream).

close_socket_and_throw(SocketId, Exception) :-
    socket:tcp_close_socket(SocketId),
    throw(mongo_error('could not connect to server', [Exception])).

socket_read(Socket, ReadStream) :-
    mongo_util:get_nth1_arg(Socket, 1, ReadStream).

socket_write(Socket, WriteStream) :-
    mongo_util:get_nth1_arg(Socket, 2, WriteStream).

%%  free_socket(+Socket) is det.
%
%   Frees any resources associated with Socket, rendering it unusable.

free_socket(Socket) :-
    socket_read(Socket, ReadStream),
    socket_write(Socket, WriteStream),
    core:close(ReadStream, [force(true)]),
    core:close(WriteStream, [force(true)]).

%%  send_bytes(+Socket, +Bytes) is det.
%
%   True if Bytes are sent (and flushed) over Socket.

send_bytes(Socket, Bytes) :-
    socket_write(Socket, WriteStream),
    send_bytes_and_flush(Bytes, WriteStream).

send_bytes_and_flush(Bytes, WriteStream) :-
    core:format(WriteStream, '~s', [Bytes]),
    core:flush_output(WriteStream).

%%  receive_n_bytes(+Socket, +N, -Bytes) is det.
%
%   True if Bytes is the next N bytes received over Socket.

receive_n_bytes(Socket, N, Bytes) :-
    socket_read(Socket, ReadStream),
    receive_n_bytes_aux(ReadStream, N, Bytes).

receive_n_bytes_aux(_ReadStream, 0, []) :- !.
receive_n_bytes_aux(ReadStream, N, [Byte|Bytes]) :-
    core:get_byte(ReadStream, Byte),
    N1 is N - 1,
    receive_n_bytes_aux(ReadStream, N1, Bytes).

So with the lines

socket:tcp_socket(SocketId),
socket:tcp_connect(SocketId, Host:Port, ReadStream, WriteStream),..

Prolog creates an INET-domain stream-socket. I'm using MS-Windows, and if the socket library is not yet initialised, this will also initialise the library.

But I'm getting this error:

Error when trying to make new tcp-connection

So here are my questions:

  1. Am I trying to make the new connection with the wrong file?
  2. The Socket-library should be loaded automatically or do I have to put the prolongo-files into the the path "C:\Program Files\swipl\library"?
  3. How to interpret this error above?
Johnny
  • 146
  • 14
  • Is this specifically for SWI-Prolog? – Guy Coder Mar 12 '21 at 16:10
  • Yes it is: https://github.com/khueue/prolongo – Johnny Mar 12 '21 at 16:33
  • 2
    If you are willing to try ODBC on SWI-Prolog then see: [SWI-Prolog connecting to PostgreSQL via ODBC](https://swi-prolog.discourse.group/t/swi-prolog-connecting-to-postgresql-via-odbc/2404) While parts are PostgreSQL specific, many parts are DB agnostic. If you can get this working you can use it as a stepping stone for your needs. – Guy Coder Mar 12 '21 at 17:59
  • Cross posted at [SWI-Prolog forum](https://swi-prolog.discourse.group/t/how-to-connect-to-mongodb-server-via-swi-prolog/3715?u=ericgt). – Guy Coder Mar 13 '21 at 13:46

1 Answers1

0

ODBC is how SWI Prolog connects to whatever database. I've just gone through this process once again for linking SWI Prolog to Postgresql, and it can be fiddly.

Firstly, you need to check this at the swipl command line:

use_module(library(odbc)).

If you get ERROR: source_sink `library(odbc)' does not exist (as I just did on a fresh Arch Linux installation), the driver needed by SWI Prolog is missing. On a Linux system, that means you need to install unixodbc. I build SWI Prolog from source, and ODBC is automatically included if a supported ODBC library is in the system.

The next part is installing ODBC support for your database (Google found https://www.mongodb.com/blog/post/odbc-driver-for-the-mongodb-connector-for-business-intelligence, but I don't have experience with MongoDB).

Then you need to configure ODBC, which on Linux involves creating a ~/.odbc.ini file. I don't use Windows, so don't know how to do it there. I've written some notes on how I got Postgresql and SWI Prolog to communicate at https://github.com/roblaing/swipl-webapp-howto/tree/master/unit3

joeblog
  • 1,101
  • 11
  • 17
  • As I want to implement this in combination with the MongoDB Community Edition, this leeds to the [problem](https://stackoverflow.com/questions/66122411/mongodb-connect-through-odbc) that you have to use a third party driver. Even in the linked post there is no garanty it works, because the API's are customized only for other plattforms like Python or Excel and not Prolog. At least I haven't found a general ODBC-driver for the MongoDB Community Edition. – Johnny Mar 30 '21 at 19:06