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               ] ).

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.

author
- Nicos Angelopoulos,
version
- 0.5 2018/3/18, fix single quote in db_holds/3, added db_max/4 and db_min/4 and examples/exam1.pl
- 0.4 + 0.3, 2016/12/22, fix code-list and enable strings as db fact arguments (and wrap of back-end loading)
- 0.2, 2016/9/18, allow mass asserts in prosqlite interface
- 0.1.0, 2013/11/1
See also
- http://stoics.org.uk/~nicos/sware/db_facts/
- files in examples/ directory
- also available as a SWI pack http://www.swi-prolog.org/pack/list
- doc/Releases.txt
license
- Perl Artistic License
To be done
- build data structures so we don't interrogate the dbs about column names and the such all the time

*/

   52:- use_module( library(debug) ).   53
   54/* defaults and settings */
 db_enabled_library(-Lib)
Lib is a db backend library enabled in this run. Lib is in {odbc,sqlite}. */
   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  */
 db_version(-Version, -Date)
The current version. Version is a Mj:Mn:Fx term, and date is a 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(+Conn, +Goal)
Very simple interface for creating tables via a term representation (Goal). Goal should share functor name and arity with the table to be creating at the database identified by Conn. Arguments of Goal should be either - or + pairs. First term of the pair should be the table name and second should be its type. The column of a + pair is taken to be part of the primary key.
     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(+Goal)
Call 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(+Goal, -Affected)
Call 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 ).
 db_assert(+Conn, +Goal, -Affected)
Assert a table row to table matching Goal of db connection Conn. Affected is the number of rows affected by the operation.

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(+Goal)
Call 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 ).
 db_holds(+Conn, +Goal)
Goal is partially instantiated at call, returning at backtracing all matching rows from corresponding table belonging to connection Conn.

*/

  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(+Goal)
Call 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(+Goal, -Affected)
Call 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(+Conn, +Goal, -Affected)
Remove all rows that correspond to the table from SQLite database identified by Conn and is named by Goal's name. The arity of Goal should also match the arity of the table to be deleted. Ground arguments are added to the Where part of the DELETE SQL statement at their respective column locations. Affected is the number of rows affected by the operation.
     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 ).
 db_goal_connection(+Goal, -Conn)
Locate connection serving table matching to Goal.

*/

  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     !.
 db_table(+Conn, -Table) is nondet
 db_table(+Conn, ?Table, -Facet) is nondet
Table is a table of database Conn. Facet is a property of table.
  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 ).
 db_table_column(+Conn, -Table, -Column)
 db_table_column(+Conn, -Table, -Column, -Facet)
Table is a table in connection. Column is a column of Table and Facet is an aspect of this Column as supported by the underlying connection manager.
  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 ).
 db_max(+Conn, +Table, +ArgOrClm, -Max)
Find the max value for a Table, at column ArgOrClm (see db_table_column_name/4).

*/

  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    !.
 db_min(+Conn, +Table, +ArgOrClm, -Min)
Find the min value for a Table, at column ArgOrClm (see db_table_column_name/4).

*/

  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    !.
 db_table_column_name(+Conn, +Table, +ArgPosOrClm, -Clm)
Get the column name, corresponding to the an integer, indicating position of column in Table's arity, or a column name. */
  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    ).
 db_query(+Conn, +Sql, -Row)
Get Row at a time from quering database handle Conn, with Sql statement.

*/

  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 ).
 db_current_connection(?Conn)
Conn is a currently open db connection. */
  431db_current_connection( Conn ) :-
  432     db_current_connection( Conn, _Type ).
 db_current_connection(?Conn, -Type)
True iff Conn is a current db connection of (db_facts) 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.
 db_disconnect(+Conn)
Disconnect from an ODBC or proSQLite connection.

*/

  470db_disconnect( Conn ) :-
  471     db( disconnect(Conn) ).
 db_date_sql_atom(Date, Sql)
Convert between a Prolog date/3 term and an Sql atom. The conversion is bidirectional. */
  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*/
 atom_replace(+Atom, +What, +With, -New)
Replace all occurances of What in Atom with With to produce New.
  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 ).
 atom_break_at(+Atom, +Breaks, -Pfx, -Psf)
Break an atom at Occurances of Breaks sub atom. Pfx = Prefix before Break, and Psf = Postfix, after the break.
  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
  722prolog:message(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] ]