1:- module( db_facts,
2 [ db_create/2, % +Conn, +Fact
3 db_assert/1, % +Fact
4 db_assert/2, % +Fact, -Aff
5 db_assert/3, % +Conn, +Fact, -Aff
6 db_holds/1, % +Conn, -Fact
7 db_holds/2, % +Conn, -Fact
8 db_retractall/1, % +Fact
9 db_retractall/2, % +Fact, -Aff
10 db_retractall/3, % +Conn, +Fact, -Aff
11 db_query/3, % +Conn, +SQL, -Res
12 db_table/2, % +Conn, +Table
13 db_table/3, % +Conn, +Table, -Facet
14 db_table_column/3, % +Conn, ?Table, ?Col
15 db_table_column/4, % +Conn, ?Table, ?Col, ?Facet
16 db_enabled_library/1, % ?Lib
17 db_current_connection/1, % ?Conn
18 db_current_connection/2, % ?Conn, -Type
19 db_disconnect/1, % +Conn
20 db_date_sql_atom/2, % ?Date, ?SQLatom
21 db_goal_connection/2, % +Goal, -Conn
22 db_max/4, % +Conn, +Table, +Arg, -Max
23 db_min/4, % +Conn, +Table, +Arg, -Min
24 db_version/2
25 ] ).
52:- use_module( library(debug) ). 53 54/* defaults and settings */
62:- dynamic( db_enabled_library/1 ). 63 64db_enable( Lib, _LibName, Enabled ) :- 65 catch( use_module(library(Lib)), _, fail ), 66 !, 67 retractall( db_enabled_library(Enabled) ), 68 assert( db_enabled_library(Enabled) ). 69db_enable( _Lib, LibName, _Enabled ) :- 70 write( user_error, LibName ), 71 Mess = ' not available. Db_facts through this back-end disabled.', 72 write( user_error, Mess ), 73 nl( user_error ), 74 nl( user_error ). 75 76:- db_enable( prosqlite, 'proSQLite', sqlite ). 77:- db_enable( odbc, odbc, odbc ). 78 79/* Interface predicates */
date(Y,M,D)
term.
?- db_version( 0:5:0, date(2018,3,18) ). true.
*/
90% db_version( 0:4:0, date(2016,12,22) ). 91db_version( 0:5:0, date(2018,3,18) ).
db_create( phones_db, phones(name+text,telephone-text,address-text) )
*/
104db_create( Conn, Goal ) :- 105 Goal =.. [Name|Args], 106 create_pairs_atoms_keys( Args, Patoms, Keys ), 107 atomic_list_concat( Patoms, ',', Fields ), 108 atomic_list_concat( Keys, ',', Primary ), 109 Cre = 'Create Table', 110 PrimK = 'Primary Key', 111 atomic_list_concat( [Cre,Name,'(',Fields,',',PrimK,'(',Primary,') );'], ' ', Create ), 112 debug( db_facts, 'Create statement: ~w', Create ), 113 sqlite_query( Conn, Create, _Res ). 114 115create_pairs_atoms_keys( [], [], [] ). 116create_pairs_atoms_keys( [H|T], [A|As], Keys ) :- 117 create_pair_atom( H, A ), 118 create_key( H, Keys, TKeys ), 119 create_pairs_atoms_keys( T, As, TKeys ). 120 121create_pair_atom( H, A ) :- 122 ( H=N+T; H=N-T ), 123 !, 124 create_type_atom( T, Tatom ), 125 atomic_list_concat( [N,Tatom], ' ', A ). 126 127% fixme [] for enum() is untested, in addition it doesnt work in sqlite... 128create_type_atom( [H|T], Tatom ) :- 129 !, 130 atomic_list_concat( [H|T], ',', ValsAtom ), 131 atomic_list_concat( ['enum(',ValsAtom,')'], Tatom ). 132create_type_atom( Atom, Atom ). 133 134create_key( K+_T, [K|TKeys], TKeys ) :- !. 135create_key( _, Keys, Keys ).
db_assert( Conn, Goal, _Aff )
for the
implied connection Conn for table that corresponds
to the supplied Goal.
*/
145db_assert( Goal ) :-
146 db_assert( Goal, _Affected ).
db_assert( Conn, Goal, Affected )
for the
implied connection Conn for table that corresponds
to the supplied Goal.
*/
156db_assert( Goal, Affected ) :-
157 db_goal_connection( Goal, Conn ),
158 db_assert( Conn, Goal, Affected ).
As of db_facts v0.2 Goal can be a list of Goals with all goals asserted in a single Instert operation.
*/
170db_assert( Conn, Goals, Affected ) :- 171 % fixme: this is is SQLITE specific for now, maybe we can assume ODBC allow multiple value() 172 is_list( Goals ), 173 !, 174 % see http://stackoverflow.com/questions/1609637/is-it-possible-to-insert-multiple-rows-at-a-time-in-an-sqlite-database 175 db_query(Conn,'BEGIN TRANSACTION', _ ), 176 maplist( db_assert(Conn), Goals, Affs ), 177 db_query( Conn, 'COMMIT', _ ), 178 maplist( arg(1), Affs, AffNs ), 179 sumlist( AffNs, AffectedNum ), 180 Affected = row(AffectedNum). 181 182db_assert( Conn, Goal, Affected ) :- 183 ground( Goal ), 184 185 Goal =.. [Table|Args], 186 db_table_columns( Conn, Table, Clms ), 187 db_current_connection( Conn, ConT ), 188 Ins = 'Insert into ', 189 190 fact_args_term( Args, Clms, Goal, FATerm ), 191 fa_value( known, FATerm, KClms, KVals ), 192 maplist( dquote(ConT), KVals, QVals ), 193 atomic_list_concat( QVals, ',', CVals ), 194 195 atomic_list_concat( KClms, ',', CClms ), 196 atomic_list_concat( [Ins,Table,' (',CClms,') Values ','(',CVals,')'], Insert ), 197 debug( db_facts, 'Assert query: ~w', [Insert] ), 198 ( db_query(Conn,Insert,Affected) -> 199 true 200 ; 201 db_error( db_assert_failure(Goal,Insert) ) 202 ).
db_holds( Conn, Goal )
for the
implied connection Conn for table that corresponds
to the supplied Goal.
*/
212db_holds( Fact ) :-
213 db_goal_connection( Fact, Conn ),
214 db_holds( Conn, Fact ).
*/
224db_holds( Conn, Goal ) :-
225 Goal =..[Table|Args],
226 db_table_columns( Conn, Table, Clms ),
227 fact_args_term( Args, Clms, Goal, FATerm ),
228 fa_value( known, FATerm, KClms, KVals ),
229 fa_value( unown, FATerm, UClms, UVals ),
230 sql_clm_value_pairs_to_where( KClms, KVals, Where ),
231 % next line untested
232 ( UClms == [] -> % then we are asking for confiramtion only
233 UnC = '*', UnV = KVals % can only succeed once in dbs.
234 ;
235 atomic_list_concat( UClms, ',', UnC ),
236 UnV = UVals
237 ),
238 atomic_list_concat( ['Select ',UnC,'From',Table,Where], ' ', Sql ),
239 Row =.. [row|UnV],
240 db_query( Conn, Sql, Row ).
db_retractall(Conn,Goal,_Aff)
for the
implied connection Conn for table that corresponds
to the supplied Goal.
*/
250db_retractall( Goal ) :-
251 db_retractall( Goal, _Affected ).
db_retractall(Conn,Goal,Affected)
for the
implied connection Conn for table that corresponds
to the supplied Goal.
*/
261db_retractall( Goal, Affected ) :-
262 db_goal_connection( Goal, Conn ),
263 db_retractall( Conn, Goal, Affected ).
db_retractall( uniprot, secondary_accessions(_,'P64943'), A ).
*/
280db_retractall( Conn, Goal, Affected ) :-
281 Goal =.. [Table|Args],
282 db_table_columns( Conn, Table, Clms ),
283 fact_args_term( Args, Clms, Goal, FATerm ),
284 fa_value( known, FATerm, KClms, KVals ),
285 sql_clm_value_pairs_to_where( KClms, KVals, Where ),
286 db_retractall_where( Where, Conn, Table, Affected ).
*/
293db_goal_connection( [Goal|_], Conn ) :- 294 !, 295 db_goal_connection( Goal, Conn ). 296db_goal_connection( Goal, Conn ) :- 297 functor( Goal, Pname, _Arity ), 298 db_current_connection( Conn ), 299 db( current_table(Conn,Pname) ), 300 !. 301db_goal_connection( _Goal, Conn ) :- 302 ground( Conn ), 303 \+ db_current_connection( Conn ), 304 !, 305 db_error( not_a_known_connection(Conn) ). 306db_goal_connection( Goal, Conn ) :- 307 ground( Conn ), 308 functor( Goal, Pname ), 309 \+ db( current_table(Conn,Pname) ), 310 !, 311 db_error( not_table_in_this_db(Pname,Conn) ). 312db_goal_connection( Goal, Conn ) :- 313 db_error( goal_connection_mismatch(Goal,Conn) ). 314 315% following not currently used. 316db_goal_connection_arity( Goal, Conn ) :- 317 functor( Goal, Pname, Arity ), 318 db_current_connection( Conn ), 319 db( current_table(Conn, Pname, arity(Arity)) ), 320 !.
330db_table( Conn, Table ) :- 331 db_current_connection( Conn, Type ), 332 db_type_table( Type, Conn, Table ). 333 334db_type_table( sqlite, Conn, Table ) :- 335 sqlite_current_table( Conn, Table ). 336 337db_type_table( odbc, Conn, Table ) :- 338 odbc_current_table( Conn, Table ). 339 340db_table( Conn, Table, Facet ) :- 341 db_current_connection( Conn, Type ), 342 db_type_table( Type, Conn, Table, Facet ). 343 344db_type_table( sqlite, Conn, Table, Facet ) :- 345 sqlite_current_table( Conn, Table, Facet ). 346 347db_type_table( odbc, Conn, Table, Facet ) :- 348 odbc_current_table( Conn, Table, Facet ).
358db_table_column( Conn, Table, Column ) :- 359 db_current_connection( Conn, Type ), 360 db_type_table_column( Type, Conn, Table, Column ). 361 362db_type_table_column( sqlite, Conn, Table, Column ) :- 363 sqlite_table_column( Conn, Table, Column ). 364 365db_type_table_column( odbc, Conn, Table, Column ) :- 366 odbc_table_column( Conn, Table, Column ). 367 368db_table_column( Conn, Table, Column, Facet ) :- 369 db_current_connection( Conn, Type ), 370 db_type_table_column( Type, Conn, Table, Column, Facet ). 371 372db_type_table_column( sqlite, Conn, Table, Column, Facet ) :- 373 sqlite_table_column( Conn, Table, Column, Facet ). 374 375db_type_table_column( odbc, Conn, Table, Column, Facet ) :- 376 odbc_table_column( Conn, Table, Column, Facet ).
*/
383db_max( Conn, Table, ArgPrv, Max ) :-
384 db_table_column_name( Conn, Table, ArgPrv, Cnm ),
385 atomic_list_concat( ['SELECT MAX(',Cnm,') FROM ',Table,';'], Sql ),
386 db_query( Conn, Sql, Row ),
387 Row = row(Max),
388 !.
*/
395db_min( Conn, Table, ArgPrv, Min ) :-
396 db_table_column_name( Conn, Table, ArgPrv, Cnm ),
397 atomic_list_concat( ['SELECT MIN(',Cnm,') FROM ',Table,';'], Sql ),
398 db_query( Conn, Sql, Row ),
399 Row = row(Min),
400 !.
407db_table_column_name( Conn, Table, ArgPrv, Clm ) :-
408 ( integer(ArgPrv) ->
409 findall( Clm, db_table_column(Conn,Table,Clm), Clms ),
410 nth1(ArgPrv,Clms,Clm)
411 ;
412 Clm = ArgPrv
413 ).
*/
422db_query( Conn, Sql, Row ) :-
423 db_current_connection( Conn, Type ),
424 debug(db_facts,'To connection ~w, of type ~w, sending: ~a',[Conn,Type,Sql]),
425 db_type_query( Type, Conn, Sql, Row ).
431db_current_connection( Conn ) :-
432 db_current_connection( Conn, _Type ).
*/
439db_current_connection( Conn, Type ) :- 440 ground( Conn ), 441 !, 442 findall( Conn-T, db_current_connection_gen(Conn,T), CTs ), 443 ( CTs = [Conn-Type] -> 444 true 445 ; 446 CTs = [Conn-_T1,Conn-_T2|_], 447 db_error( multiple_db_handles(CTs) ) 448 ). 449 450db_current_connection( Conn, Type ) :- 451 db_current_connection_gen( Conn, Type ). 452 453db_current_connection_gen( Conn, Type ) :- 454 db_enabled_library( odbc ), 455 odbc_current_connection( Conn1, _ ), 456 Conn = Conn1, 457 Type = odbc. 458db_current_connection_gen( Conn, Type ) :- 459 db_enabled_library( sqlite ), 460 sqlite_current_connection( Conn ), 461 Type = sqlite.
*/
470db_disconnect( Conn ) :-
471 db( disconnect(Conn) ).
478db_date_sql_atom( date(Y,M,D), Sql ) :- 479 ground( Sql ), 480 !, 481 atomic_list_concat( Consts, '/', Sql ), 482 maplist( atom_number, Consts, [Y,M,D] ). 483db_date_sql_atom( date(Y,M,D), Sql ) :- 484 atomic_list_concat( [Y,M,D], '/', Sql ). 485 486% non-interface predicates 487% 488 489% take advantage of the fact that {odbc,sqlite}_predname() 490% are valid calls in both backends ! 491% 492db( Goal ) :- 493 arg( 1, Goal, Conn ), 494 db_current_connection( Conn, Type ), 495 Goal =.. [Pname|Args], 496 atomic_list_concat( [Type,Pname], '_', Gname ), 497 TypeGoal =.. [Gname|Args], 498 call( TypeGoal ). 499 500fa_value( kcols, kkv_ukv(KClms,_,_,_), KClms ). 501fa_value( kvals, kkv_ukv(_,KVals,_,_), KVals ). 502fa_value( ucols, kkv_ukv(_,_,UClms,_), UClms ). 503fa_value( uvals, kkv_ukv(_,_,_,UVals), UVals ). 504 505fa_value( known, kkv_ukv(KClms,KVals,_,_), KClms, KVals ). 506fa_value( unown, kkv_ukv(_,_,UClms,UVals), UClms, UVals ). 507 508fact_args_term( ArgsIn, Clms, _Goal, FATerm ) :- 509 ( (ArgsIn=[Args],is_list(Args)) -> true ; Args = ArgsIn ), 510 maplist( look_for_pair_nonvar, Args, Keys, Vals ), 511 !, 512 look_for_pairs_args_term( Keys, Vals, Clms, Kclms, Kvals, Uclms, Uvals ), 513 FATerm = kkv_ukv(Kclms,Kvals,Uclms, Uvals ). 514fact_args_term( Args, Clms, Goal, FATerm ) :- 515 correspond_args_term( Args, Clms, Goal, Kclms, Kvals, Uclms, Uvals ), 516 FATerm = kkv_ukv(Kclms,Kvals,Uclms, Uvals ). 517 518correspond_args_term( [], Clms, Goal, [], [], [], [] ) :- 519 ( Clms == [] -> 520 true 521 ; 522 db_error( insufficient_args(Goal) ) 523 ). 524correspond_args_term( [A|As], [C|Cs], Goal, Kclms, Kvals, Uclms, Uvals ) :- 525 !, 526 ( var(A) -> 527 TKclms = Kclms, 528 TKvals = Kvals, 529 Uclms = [C|TUclms], 530 Uvals = [A|TUvals] 531 ; 532 Kclms = [C|TKclms], 533 Kvals = [A|TKvals], 534 TUclms = Uclms, 535 TUvals = Uvals 536 ), 537 correspond_args_term( As, Cs, Goal, TKclms, TKvals, TUclms, TUvals ). 538 539correspond_args_term( [A|_As], Clms, Goal, _, _, _, _) :- 540 ( Clms == [] -> 541 db_error( too_many_args(Goal) ) 542 ; 543 db_error( could_not_parse_arg(A,Goal) ) 544 ). 545 546look_for_pairs_args_term( [], [], _Clms, [], [], [], [] ). 547look_for_pairs_args_term( [K|Ks], [V|Vs], Clms, Kclms, Kvals, Uclms, Uvals ) :- 548 is_one_of_columns( K, Clms ), 549 ( var(V) -> 550 TKclms = Kclms, 551 TKvals = Kvals, 552 Uclms = [K|TUclms], 553 Uvals = [V|TUvals] 554 ; 555 Kclms = [K|TKclms], 556 Kvals = [V|TKvals], 557 TUclms = Uclms, 558 TUvals = Uvals 559 ), 560 look_for_pairs_args_term( Ks, Vs, Clms, TKclms, TKvals, TUclms, TUvals ). 561 562look_for_pair_nonvar( Var, _, _ ) :- var(Var), !, fail. 563look_for_pair_nonvar( Pair, A, B ) :- 564 look_for_pair_silent( Pair, A, B ). 565 566look_for_pair_silent( A=B, A, B ). 567look_for_pair_silent( A-B, A, B ). 568look_for_pair_silent( A:B, A, B ). 569 570 571is_one_of_columns( Clm, Columns ) :- 572 memberchk( Clm, Columns ), 573 !. 574is_one_of_columns( Clm, Columns ) :- 575 db_error( unknown_column(Clm,Columns) ). 576 577% this could be an interface pred.... 578db_table_columns( Conn, Name, Cols ) :- 579 findall( Col, db(table_column(Conn,Name,Col)), Cols ), 580 ( Cols == [] -> 581 % we can check if connection is valid here, 582 % and send a different error if that's the case. 583 db_error( failed_to_get_columns(Conn,Name) ) 584 ; 585 true 586 ). 587 588db_connection_goal( Conn, Pred, Args, Goal ) :- 589 db_current_connection( Conn, Type ), 590 atomic_list_concat( [Type,Pred], '_', dbPred ), 591 Goal =.. [dbPred|Args]. 592 593db_type_query( sqlite, Conn, Sql, Row ) :- 594 sqlite_query( Conn, Sql, Row ). 595db_type_query( odbc, Conn, Sql, Row ) :- 596 odbc_query( Conn, Sql, Row ). 597 598db_retractall_where( '', Conn, Name, 0 ) :- 599 !, 600 write( user_error, 'Refusing to delete whole connection/table':Conn/Name ), 601 nl( user_error ), 602 fail. 603db_retractall_where( Where, Conn, Name, Affected ) :- 604 Del = 'Delete from', 605 atomic_list_concat( [Del,Name,Where], ' ', Sql ), 606 db_query( Conn, Sql, Row ), 607 Row = row(Affected). 608 609sql_clm_value_pairs_to_where(Clms, Vals, Where) :- 610 sql_clm_value_pairs_to_where_conjunction(Clms, Vals, Conjunction), 611 sql_where_conjunction_to_where(Conjunction, Where). 612 613sql_where_conjunction_to_where('', '' ) :- !. 614sql_where_conjunction_to_where(Conjunction, Where ) :- 615 atom_concat( 'Where ', Conjunction, Where ). 616 617sql_clm_value_pairs_to_where_conjunction([], [], ''). 618sql_clm_value_pairs_to_where_conjunction([K|Ks], [V|Vs], Where) :- 619 sql_clm_value_pairs_to_where_conjunction( Ks, Vs, InWhere ), 620 sql_clm_and_val_to_sql_equals_atom(K, V, KVAtm), 621 ( InWhere == '' -> 622 Where = KVAtm 623 ; 624 atomic_list_concat([KVAtm, ' AND ', InWhere], Where) 625 ). 626 627sql_clm_and_val_to_sql_equals_atom(K, V, KVAtm) :- 628 ( number(V) -> 629 atom_number(Vatm, V), 630 atom_concat('=',Vatm,EqV) 631 ; 632 atomic_list_concat( Parts, '\'', V ), 633 atomic_list_concat( Parts, '\'\'', Vdb ), 634 atom_concat(Vdb, '\'', VDsh), 635 atom_concat('=\'',VDsh,EqV) 636 ), 637 atom_concat(K, EqV, KVAtm). 638 639% fixme: date ? 640dquote( _, date(Y,M,D), Quoted ) :- 641 !, 642 atomic_list_concat( ['"',Y,'/',M,'/',D,'"'], Quoted ). 643dquote( _, Val, Quoted ) :- 644 number( Val ), 645 !, 646 Quoted = Val. 647dquote( ConT, Val, Quoted ) :- 648 atom( Val ), 649 !, 650 ( ConT == sqlite -> atom_replace( Val, '"', '""', Esc ); 651 Esc = Val ), 652 atomic_list_concat( ['"',Esc,'"'], Quoted ). 653dquote( _ConT, Val, Quoted ) :- 654 is_list( Val ), 655 !, 656 append( [0'"|Val], [0'"], QuotedCs ), 657 atom_codes( Quoted, QuotedCs ). 658dquote( ConT, Val, Quoted ) :- 659 string( Val ), 660 atom_string( Atm, Val ), 661 ( ConT == sqlite -> atom_replace( Atm, '"', '""', Esc ); 662 Esc = Val ), 663 atomic_list_concat( ['"',Esc,'"'], Quoted ). 664 665/* 666dquote( Val, Quoted ) :- 667 number( Val ), 668 !, 669 Quoted = Val. 670dquote( Val, Quoted ) :- 671 atom_replace( Val, '"', '""', Esc ), 672 atom_codes( Esc, Codes ), 673 CPairs = [0'"-[0'",0'"],946-"beta",947-"gamma"], 674 dquote_cs( Codes, CPairs, Qcs ), 675 atom_codes( Quoted, [0'"|Qcs] ). 676 % atomic_list_concat( ['"',Esc,'"'], Quoted ). 677 678dquote_cs( [], _CBys, [0'"] ). 679dquote_cs( [H|T], CBys, Clean ) :- 680 ( memberchk(H-By,CBys) -> 681 sew( By, Clean, Tail ) 682 ; 683 Clean = [H|Tail] 684 ), 685 dquote_cs( T, CBys, Tail ). 686 687sew( [], Tail, Tail ). 688sew( [H|T], [H|M], Tail ) :- 689 sew( T, M, Tail ). 690*/
696atom_replace( Atom, What, With, New ) :- 697 atom_break_at( Atom, What, Pfx, Psf ), 698 !, 699 atom_replace( Psf, What, With, NewPsf ), 700 atomic_list_concat( [Pfx,NewPsf], With, New ). 701atom_replace( New, _What, _With, New ).
708atom_break_at( Atom, Breaks, Pfx, Psf ) :- 709 sub_atom( Atom, Bef, _Len, Aft, Breaks ), 710 sub_atom( Atom, 0, Bef, _, Pfx ), 711 Start is Bef + 1, 712 sub_atom( Atom, Start, Aft, _, Psf ). 713 714%-Section error handling. 715db_error( Term ) :- 716 Type = error, 717 print_message( Type, db(Term) ), 718 abort. 719 720:- multifile prolog:message//1. 721 722prologmessage(db(Message)) --> 723 message(Message). 724 725message( failed_to_get_columns(Conn,Tname) ) --> 726 ['Failed to get columns for table ~q on connection ~q.' - [Tname,Conn] ]. 727message( db_assert_failure(Goal,Insert) ) --> 728 ['Failed to assert fact ~q by sql command ~a.' - [Goal,Insert] ]. 729message( multiple_db_handles([Conn-_|_]) ) --> 730 ['Multiple entries for single connection alias ~q.' - [Conn] ]. 731% not currently used : 732message( operation_needs_all_columns(Op,Cols,Goal) ) --> 733 ['Operation ~a needs all columns, instead of ~q, in fact ~q' 734 - [Op,Cols,Goal] ]. 735message( insufficient_args(Goal) ) --> 736 ['Insufficient number of arguments in db_fact, ~q.' - [Goal] ]. 737message( too_many_args(Goal) ) --> 738 ['Too many arguments in db_fact, ~q.' - [Goal] ]. 739message( could_not_parse_arg(A,Goal) ) --> 740 ['Could not parse argument ~q in db_fact, ~q.' - [A,Goal] ]. 741message( unknown_column(Clm,Columns) ) --> 742 [ 'Unkown column, ~q expected one in ~q.' - [Clm,Columns] ]. 743message( not_a_known_connection(Conn) ) --> 744 ['Not a known connection:~q.' - Conn ]. 745message( not_table_in_this_db(Pname,Conn) ) --> 746 ['Cannot locate table ~q in database connected at ~q.'- [Pname,Conn] ]. 747message( goal_connection_mismatch(Goal,_Conn) ) --> 748 [ 'Cannot establish parent db for fact, ~q.' - [Goal] ]
a term based interface to the ODBC and SQLite libaries.
This library serves two purposes. First, term structures can be used to interact with SQL databases and second, to provide a common abstraction layer for ODBC and proSQLite libraries of SWI-Prolog.
This library is debug/1 aware: call
debug(db_facts)
to see what is sent to the SQL engine.examples/exam1.pl
doc/Releases.txt
*/