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