A Prolog library to connect to PostgreSQL databases
The library itself are just four Prolog files (postgresql.pl, sql_query.pl, messages.pl and types.pl). They need to be in the same folder. An easy way to install this library in your project is copying that files. Other way is using Git submodules to get this whole folder, and load the postgresql.pl file from there.
git submodule add https://github.com/aarroyoc/postgresql-prolog postgresql
The library provides four predicates: connect/6
, sql/3
, query/3
and query/4
.
connect( User, Password, Host, Port, Database, -Connection)
Connects to a PostgreSQL server and tries to authenticate using password
scheme. This is the only authentication method supported right now. Please, note that this auth method is not the default in some PostgreSQL setups, you changes are needed. If you're running PostgreSQL in Docker, you need to set the environment variable POSTGRES_HOST_AUTH_METHOD
to password
.
sql( Connection, QueryDSL, -Result)
Executes a SQL query using the Prolog DSL. This the recommended way to do queries as it is the safest (it escapes all strings). The DSL right now is limited to a few keywords. Feel free to open a PR to grow the DSL if you need it. The DSL is composed a list of commands, each command may contain different things. The DSL makes some incorrect queries invalid but it isn't a full fledged SQL syntax checker.
Items of the DSL:
select(col1, col2, ...)
- equivalent toSELECT col1, col2, ... . Functions are allowed. For example:
select(sum(visits))is valid and will generate
SELECT sum(visits)`.from(table)
- equivalent toFROM table
natural_join(table)
- equivalent toNATURAL JOIN table
join(table)
/inner_join(table)
- equivalent toINNER JOIN table
. Requires aon
orusing
after.left_join(table)
/left_outer_join
- equivalent toLEFT OUTER JOIN table
. Requires aon
orusing
after.right_join(table)
/right_outer_join
- equivalent toRIGHT OUTER JOIN table
. Requires aon
orusing
after.full_join(table)
/full_outer_join
- equivalent toFULL OUTER JOIN table
. Requires aon
orusing
after.on(Cond)
- equivalent toON Cond
. Seewhere
for details.using(col) - equivalent to
USING (col)`.where(Cond)
- equivalent toWHERE Cond
. Cond is a Prolog condition. Meaning that you can use parens, AND is (,), OR is (;) and =, =, >= and =< are used instead of the SQL operators. Strings that appear in the condition are escaped correctly. This is safe.group_by(col1)
- equivalent to GROUP BY col1.order_by(kind(col1), kind(col2),...)
- equivalent to ORDER BY col1 KIND, col2 KIND, .... kind must be eitherasc
ordesc
.offset(N)
- equivalent toOFFSET N ROWS
.fetch_first(N, type)
- equivalent toFETCH FIRST N ROWS type
. Type can beonly (ONLY)
orwith_ties (WITH TIES)
.insert_into(table, [col1, col2,...])
- equivalent toINSERT INTO table (col1, col2, ...)
. Must be followed byvalues
.values(val1, val2, ...)
- equivalent toVALUES (val1, val2, ...)
. Strings here are escaped so it's safe.returning(col1, col2, ...)
equivalent toRETURNING col1, col2
.update(table)
- equivalent toUPDATE table
. Must be followed byset
set(Sets)
- equivalent toSET Col1 = Val1, ...
. Similar to WHERE but only = is allowed. Optionally, you can add awhere
after aset
.delete(table)
- equivalent toDELETE FROM table
. Must be followed bywhere
.
Examples:
QueryDSL = [select(title), from(posts), where(lang = "es"), order_by(desc(date)), offset(10), fetch_first(5, only)],
SQL = "SELECT title FROM posts WHERE lang = $1 ORDER BY date DESC OFFSET 10 ROWS FETCH FIRST 5 ROWS ONLY"
QueryDSL = [select('posts.title','author.name'), from(posts), join(author), on('author.author_id' = 'posts.author_id')]
SQL = "SELECT posts.title,author.name FROM posts INNER JOIN author ON author.author_id = posts.author_id"
QueryDSL = [select(title), from(posts), where((lang = "es";is_null(lang))), order_by(desc(date), asc(title))]
SQL = "SELECT title FROM posts WHERE (lang = $1) OR (lang IS NULL) ORDER BY date DESC,title ASC"
QueryDSL = [insert_into(test_table, [name]), values("test")]
SQL = "INSERT INTO test_table (name) VALUES ($1)"
QueryDSL = [update(post),set((lang = "es", price = 99)),where(lang = "fr")]
SQL = "UPDATE post SET lang = $1,price = 99 WHERE lang = $2"
The Result var contains the result exection of the query. It can be:
- error(ErrorString)
- data(Rows)
An OK response would be a data response with empty Rows.
query( Connection, Query, -Result)
Executes a SQL query string over a connection. Result can be:
- ok
- error(ErrorString)
- data(ColumnDescription, Rows)
ok is returned if the query doesn't output a table (INSERT, UPDATE, DELETE, CREATE TABLE, ...) and succeeds.
error(ErrorString) is returned if an error is found.
data(ColumnDescription, Rows) is returned when a query outputs a table (SELECT). ColumnDescription is a list of column names and Rows is a list of a list of each cell value.
query( Connection, QueryEscaped, Data, -Result)
Executes a SQL query string over a connection. In contrast to query/3
, here the query needs to be a template and the vars are passed in the Data list. This is safer than query/3
. Result is the same as sql/3
.
:- use_module('postgresql').
test :-
postgresql:connect("postgres", "postgres", '127.0.0.1', 5432, "postgres", Connection),
postgresql:query(Connection, "DROP TABLE IF EXISTS test_table", ok),
postgresql:query(Connection, "CREATE TABLE test_table (id serial, name text)", ok),
postgresql:sql(Connection, [insert_into(test_table, [name]), values("test")], data([])),
postgresql:sql(Connection, [select(id, name), from(test_table), where(name = "test")], Rows),
Rows = data([["1", "test"]]),
postgresql:sql(Connection, [update(test_table), set(name = "test2"), where(id = 1)], data([])),
postgresql:sql(Connection, [select(id, name), from(test_table), where(name = "test")], Rows2),
Rows2 = data([]).