4
$\begingroup$

I have the following code for a simple key value store for binary data:

BeginPackage["keyValueStore`"];
{open, insert, get, close};
Begin["`Private`"];
Needs["DatabaseLink`"];

open[] := (
   $connection = OpenSQLConnection[JDBC["SQLite(Memory)",""]];
   SQLCreateTable[$connection, SQLTable["data"], {
     SQLColumn["key", "DataTypeName" -> "TEXT"],
     SQLColumn["value", "DataTypeName" -> "BLOB", "Nullable" -> True]
   }];
);

insert[key_, data_] := SQLInsert[$connection, 
  "data", {"key", "value"}, {key, SQLBinary[data]}];

get[key_] := Replace[
  SQLSelect[$connection, "data", "value", SQLColumn["key"] == key],
  {{SQLBinary[data_]}} :> data
];

close[] := CloseSQLConnection[$connection];

End[];
EndPackage[];

which one could use like this:

open[];
insert["one", {1, 2, 3}]
get["one"]
close[];

if you try this, you will find that inserting data will not work but throw a message like:

DatabaseLink`JDBC::error: "Illegal value: DatabaseLink`SQLBinary[{1, 2, 3}]"

now I can repair that by either doing:

BeginPackage["keyValueStore`",{"DatabaseLink`"}]

(which I don't want) or by replacing SQLBinary with Symbol["SQLBinary"] in the code. Both seem to repair a behavior considering the context of SQLBinary that I would consider a bug, but maybe just don't understand correctly what is going on. Would anyone agree that this is not as one would expect and should be repaired? Or can someone enlighten me why this is what I should expect?

$\endgroup$
  • $\begingroup$ What happens if you move the Needs to before the Begin? $\endgroup$ – Szabolcs Sep 12 '17 at 9:12
  • $\begingroup$ But is DatabaseLink`SQLBinary[{1, 2, 3}] illegal or what? What should be the context of SQLBinary? $\endgroup$ – Kuba Sep 12 '17 at 9:29
  • $\begingroup$ @Szabolcs: that will also not work. It seems to be necessary that either Databaselink` is on $ContextPath or that SQLBinary is created in the current $Context... $\endgroup$ – Albert Retey Sep 12 '17 at 9:35
  • 1
    $\begingroup$ So it looks like DatabaseLink works only when it is in $ContextPath. The reason could be that there's some string-to-code translation somewhere in the package. Maybe through J/Link? A workaround could be to temporarily add "DatabaseLink`" to the context path while your functions is being evaluated (using InheritedBlock). But this still bothers me ... $\endgroup$ – Szabolcs Sep 12 '17 at 9:57
  • 1
    $\begingroup$ @Szabolcs: impressive, what you have detected seems to be the explanation -- and for me looks like a bug, which even could be corrected easily. I think I should file a bug report, would you aggree? $\endgroup$ – Albert Retey Sep 12 '17 at 10:10
5
$\begingroup$

In short, the reason for the behaviour you observe is this:

Some DatabaseLink functions will only work if "DatabaseLink`" is in $ContextPath at the time of their evaluation.

It does not matter how DatabaseLink was loaded. All that matters is whether its context is in $ContextPath at the time when SQLInsert is called.


The detailed reason is that part of DatabaseLink is implemented in Java, and some Mathematica expressions are being sent to Java through MathLink (J/Link). This involves encoding the symbols making up those expressions as strings. Whether they are encoded with or without a context prepended depends on the value of the current $ContextPath and $Context.

If "DatabaseLink`" is in $ContextPath,

Needs["DatabaseLink`"]

ToString[DatabaseLink`SQLBinary]
(* "SQLBinary" *)

If it is not:

Block[{$ContextPath},
 ToString[DatabaseLink`SQLBinary]
 ]
(* "DatabaseLink`SQLBinary" *)

The Java code does some explicit string comparisons of some symbol names with "SQLBinary", and fails if it finds "DatabaseLink`SQLBinary" instead.

The relevant code is lines 23, 110 and 266 of this file:

FileNameJoin[{DirectoryName@FindFile["DatabaseLink`"], "..", "java", 
  "com", "wolfram", "databaselink", "SQLStatementProcessor.java"}]

A possible workaround is to temporarily insert the required context in the context path in every relevant function in your package.

For example,

dbl = Function[expr, 
   Internal`InheritedBlock[{$ContextPath}, 
    PrependTo[$ContextPath, "DatabaseLink`"]; expr], HoldAllComplete];

insert[key_, data_] := dbl@SQLInsert[...]

Related:

$\endgroup$
  • $\begingroup$ thanks for the thorough inspection, I accepted it. It seems now very clear what happens and how we can implement workarounds. But I think you would agree that this has a strong smell of a bug, wouldn't you? $\endgroup$ – Albert Retey Sep 12 '17 at 19:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.