Changeset: e55a2e2a6897 for MonetDB
URL: http://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=e55a2e2a6897
Modified Files:
        monetdb5/extras/pyapi/pyapi.c
        sql/include/sql_catalog.h
        sql/server/sql_parser.y
        tools/merovingian/daemon/forkmserver.c
Branch: pyapi
Log Message:

Extended the CREATE FUNCTION statement to support passing in a path to a file.
The following syntax is now valid:
CREATE FUNCTION test(i INTEGER) RETURNS INTEGER LANGUAGE PYTHON 'hello.py';
When the function is executed, the code will be loaded from the specified 
filename (or an error will be thrown if the file does not exist).
Relative paths must end with the '.py' suffix, absolute paths must start with 
the '/' character.
Relative paths will search relative to the path passed as command line 
parameter as --set funtion_basepath=<basepath>, if that is not passed the $HOME 
environment variable is used.


diffs (185 lines):

diff --git a/monetdb5/extras/pyapi/pyapi.c b/monetdb5/extras/pyapi/pyapi.c
--- a/monetdb5/extras/pyapi/pyapi.c
+++ b/monetdb5/extras/pyapi/pyapi.c
@@ -97,6 +97,18 @@ int PyAPIEnabled(void) {
             || GDKgetenv_isyes(pyapi_enableflag));
 }
 
+static char* FunctionBasePath(void);
+static char* FunctionBasePath(void) {
+    char *basepath = GDKgetenv("function_basepath");
+    if (basepath == NULL) {
+        basepath = getenv("HOME");
+    }
+    if (basepath == NULL) {
+        basepath = "";
+    }
+    return basepath;
+}
+
 static MT_Lock pyapiLock;
 static MT_Lock queryLock;
 static int pyapiInitialized = FALSE;
@@ -421,7 +433,6 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
     int retcols = !varres ? pci->retc : -1;
     PyGILState_STATE gstate = PyGILState_LOCKED;
 
-
 #ifndef HAVE_FORK
     (void) mapped;
 #endif
@@ -438,6 +449,7 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
         strcpy(string, exprStr);
         exprStr = string;
     }
+
     // If the source starts with 'NOTEST' we disable testing, kind of a 
band-aid solution but they don't pay me enough for anything better
     if (exprStr[1] == 'N' && exprStr[2] == 'O' && exprStr[3] == 'T' && 
exprStr[4] == 'E' && exprStr[5] == 'S' && exprStr[6] == 'T') {
         VERBOSE_MESSAGE("Disable testing!\n");
@@ -484,7 +496,7 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
         }
     }
 
-    // We name all the unknown arguments, if grouping is enabled there is one 
unknown argument that is the group variable, we name this 'aggr_group'
+    // We name all the unknown arguments, if grouping is enabled the first 
unknown argument that is the group variable, we name this 'aggr_group'
     for (i = pci->retc + 2; i < pci->argc; i++) {
         if (args[i] == NULL) {
             if (!seengrp && grouped) {
@@ -500,8 +512,7 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
 
     // Construct PyInput objects, we do this before any multiprocessing 
because there is some locking going on in there, and locking + forking = bad 
idea (a thread can fork while another process is in the lock, which means we 
can get stuck permanently)
     argnode = sqlfun && sqlfun->ops->cnt > 0 ? sqlfun->ops->h : NULL;
-    for (i = pci->retc + 2; i < pci->argc; i++)
-    {
+    for (i = pci->retc + 2; i < pci->argc; i++) {
         PyInput *inp = &pyinput_values[i - (pci->retc + 2)];
         if (!isaBatType(getArgType(mb,pci,i))) {
             inp->scalar = true;
@@ -513,9 +524,7 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
             else {
                 inp->dataptr = getArgReference(stk, pci, i);
             }
-        }
-        else
-        {
+        } else {
             b = BATdescriptor(*getArgReference_bat(stk, pci, i));
             if (b == NULL) {
                 msg = createException(MAL, "pyapi.eval", "The BAT passed to 
the function (argument #%d) is NULL.\n", i - (pci->retc + 2) + 1);
@@ -886,6 +895,45 @@ str PyAPIeval(Client cntxt, MalBlkPtr mb
         gstate = PyGILState_Ensure();
     }
 
+    if (sqlfun) {
+        // Check if exprStr references to a file path or if it contains the 
Python code itself
+        // There is no easy way to check, so the rule is if it starts with '/' 
it is always a file path,
+        // Otherwise it's a (relative) file path only if it ends with '.py'
+        size_t length = strlen(exprStr);
+        if (exprStr[0] == '/' || (exprStr[length - 3] == '.' && exprStr[length 
- 2] == 'p' && exprStr[length - 1] == 'y')) {
+            FILE *fp;
+            char address[1000];
+            struct stat buffer;
+            size_t length;
+            if (exprStr[0] == '/') { 
+                // absolute path
+                snprintf(address, 1000, "%s", exprStr);
+            } else {
+                // relative path
+                snprintf(address, 1000, "%s/%s", FunctionBasePath(), exprStr);
+            }
+            if (stat(address, &buffer) < 0) { 
+                msg = createException(MAL, "pyapi.eval", "Could not find 
Python source file \"%s\".", address);
+                goto wrapup;
+            }
+            fp = fopen(address, "r");
+            if (fp == NULL) {
+                msg = createException(MAL, "pyapi.eval", "Could not open 
Python source file \"%s\".", address);
+                goto wrapup;
+            }
+            fseek(fp, 0, SEEK_END);
+            length = ftell(fp);
+            fseek(fp, 0, SEEK_SET);
+            exprStr = GDKzalloc(length + 1);
+            if (exprStr == NULL) {
+                msg = createException(MAL, "pyapi.eval", MAL_MALLOC_FAIL" 
function body string.");
+                goto wrapup;
+            }
+            fread(exprStr, 1, length, fp);
+            fclose(fp);
+        }
+    }
+
     /*[PARSE_CODE]*/
     VERBOSE_MESSAGE("Formatting python code.\n");
     pycall = FormatCode(exprStr, args, pci->argc, 4, &code_object, &msg);
diff --git a/sql/include/sql_catalog.h b/sql/include/sql_catalog.h
--- a/sql/include/sql_catalog.h
+++ b/sql/include/sql_catalog.h
@@ -293,7 +293,7 @@ typedef struct sql_arg {
 #define FUNC_LANG_C   4
 #define FUNC_LANG_J   5
 #define FUNC_LANG_PY  6 /* create .. language Python */
-#define FUNC_LANG_MAP_PY  7 /* create .. language Map Python */
+#define FUNC_LANG_MAP_PY  7 /* create .. language PYTHON_MAP */
 
 #define LANG_EXT(l)  (l>FUNC_LANG_SQL)
 
diff --git a/sql/server/sql_parser.y b/sql/server/sql_parser.y
--- a/sql/server/sql_parser.y
+++ b/sql/server/sql_parser.y
@@ -295,6 +295,7 @@ int yydebug=1;
        forest_element_name
        XML_namespace_prefix
        XML_PI_target
+       function_body
 
 %type <l>
        passwd_schema
@@ -1703,6 +1704,12 @@ external_function_name:
        ident '.' ident { $$ = append_string(append_string(L(), $1), $3); }
  ;
 
+
+function_body:
+       X_BODY
+|      string
+;
+
 func_def:
     create FUNCTION qname
        '(' opt_paramlist ')'
@@ -1733,7 +1740,7 @@ func_def:
   | create FUNCTION qname
        '(' opt_paramlist ')'
     RETURNS func_data_type
-    LANGUAGE IDENT X_BODY { 
+    LANGUAGE IDENT function_body { 
                        int lang = 0;
                        dlist *f = L();
                        char l = *$10;
@@ -1790,7 +1797,7 @@ func_def:
   | create AGGREGATE qname
        '(' opt_paramlist ')'
     RETURNS func_data_type
-    LANGUAGE IDENT X_BODY { 
+    LANGUAGE IDENT function_body { 
                        int lang = 0;
                        dlist *f = L();
                        char l = *$10;
diff --git a/tools/merovingian/daemon/forkmserver.c 
b/tools/merovingian/daemon/forkmserver.c
--- a/tools/merovingian/daemon/forkmserver.c
+++ b/tools/merovingian/daemon/forkmserver.c
@@ -390,6 +390,13 @@ forkMserver(char *database, sabdb** stat
                if (embeddedpy != NULL) {
                        argv[c++] = "--set"; argv[c++] = embeddedpy;
                }
+               kv = findConfKey(ckv, "function_basepath");
+               if (kv->val != NULL) {
+                       char address[1000];
+                       snprintf(address, 1000, "function_basepath=%s", 
kv->val);
+                       argv[c++] = "--set"; 
+                       argv[c++] = address;
+               }
                if (readonly != NULL) {
                        argv[c++] = readonly;
                }
_______________________________________________
checkin-list mailing list
[email protected]
https://www.monetdb.org/mailman/listinfo/checkin-list

Reply via email to