Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Allow SPI Calls from inside of Multicorn Python #224

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
fb39f71
Added check for NULL in getRowIDColumn.
rbiro Aug 17, 2016
7b361d1
Merge remote-tracking branch 'main/master'
rbiro Jan 11, 2019
e381d74
Merge branch 'master' of https://github.com/Kozea/Multicorn
rbiro May 11, 2019
8c6a1c7
Added code to support SPI callbacks as well as release hooks.
rbiro Jun 3, 2019
ff731c8
Removed some debuging code.
rbiro Jun 5, 2019
637bf48
Added missing NULL check.
rbiro Jun 9, 2019
4cb9f0c
Latest changes that make calling postgres in the same context mostly …
rbiro Oct 22, 2019
34d80af
Added check for NULL in getRowIDColumn.
rbiro Aug 17, 2016
dc6286f
Added code to support SPI callbacks as well as release hooks.
rbiro Jun 3, 2019
23a07d9
Removed some debuging code.
rbiro Jun 5, 2019
5fe8521
Added missing NULL check.
rbiro Jun 9, 2019
b076cf9
Latest changes that make calling postgres in the same context mostly …
rbiro Oct 22, 2019
d208b20
Remove unused variable from extractColumns()
Oct 24, 2019
43c5767
Merge https://github.com/KAction/Multicorn
rbiro Nov 19, 2019
6503b52
Commented out free to see if it was a double free. Could lead to a m…
rbiro Mar 3, 2020
5732ba7
Try not freeing argtypes.
rbiro Mar 3, 2020
7fad456
Commented out code that actually calls execute to see if that's where…
rbiro Mar 3, 2020
d246b56
Deleted unnecessary iret=0 with missing ;
rbiro Mar 3, 2020
3b5dfb9
Returned the exec and got rid of a couple of free statements.
rbiro Mar 3, 2020
bc4a7ec
Made multicorn_connect count the number of entries.
rbiro Mar 3, 2020
5aec619
Removed fatal error when multicorn_connect is called more than 1 deep…
rbiro Mar 3, 2020
c1f00e3
A bunch of debugging code to find out why it's not reentrant.
rbiro Mar 3, 2020
51e44f9
Moved mylog.
rbiro Mar 3, 2020
af6f629
Added debug code to connect/disconnect.
rbiro Mar 3, 2020
ca2eede
yet more debbuing output.
rbiro Mar 3, 2020
61d817e
Must keep track of both enters and exits to properly
rbiro Mar 3, 2020
2576fca
Removed a bunch of debugging log statements.
rbiro Mar 3, 2020
ef5e053
Commented out plan free in case that's where the double free is.
rbiro Mar 4, 2020
3e94c26
Re-enable some debugging code.
rbiro Mar 5, 2020
e5c1d72
Debugging code.
rbiro Mar 5, 2020
c1a5d79
Debugging output.
rbiro Mar 5, 2020
5351802
Raise an error if we try to execute SQL without being connected to po…
rbiro Mar 5, 2020
4cdaa24
Moved mylog back to utils so we can actually load it.
rbiro Mar 5, 2020
75e2053
Don't disconnect from the SPI.
rbiro Mar 5, 2020
ad68391
Don't free anything. See if crash goes away.
rbiro Mar 5, 2020
2911f05
Turn off execute_plan to see if that's what's causing trouble.
rbiro Mar 5, 2020
e54c3d8
Turn off the other execute to see if that is what is causing trouble.
rbiro Mar 5, 2020
59c3838
commented out logging to /tmp/mylog.
rbiro Mar 5, 2020
529d6cd
Raise an exception on unknown type.
rbiro Mar 5, 2020
aa06fd2
Allow execute to go through again.
rbiro Mar 5, 2020
df3975a
spell typename incorrectly.
rbiro Mar 5, 2020
5be62aa
Allow stmt execute to run again.
rbiro Mar 5, 2020
73eefee
Start freeing memory again.
rbiro Mar 5, 2020
369c795
Don't free individually, ust the whole connection.
rbiro Mar 5, 2020
1fc33d8
Check for dropped attributes, invalid, types and bad lengths before
rbiro Mar 7, 2020
7f99678
Removed assumption that select tuples match the table.
rbiro Mar 9, 2020
e6fa705
When the tupdesc changes, warn and recompute the cinfo.
rbiro Mar 10, 2020
306d039
Converted Assert to an error with some useful info. So at least we
rbiro Mar 10, 2020
58d5588
Switch from repalloc NULL to palloc and cleanup warning for nameData.
rbiro Mar 10, 2020
0a9b781
Don't test for attlen < 0, variable length fields apparently use that.
rbiro Mar 10, 2020
8482e46
Added some debugging code and made sure the key and attribute name ma…
rbiro Mar 10, 2020
0dd4e5b
Comment potential problem in multicorn.c.
rbiro Mar 10, 2020
4e20c25
Comment out SPI_Fiish to see if it's a use after free problem.
rbiro Mar 11, 2020
6fe4795
Fixed spelling of NULL.
rbiro Mar 11, 2020
2636e6b
Try reinitializing cinfos every iteration.
rbiro Mar 11, 2020
1422cdb
Merge ../Multicorn.upstream
rbiro Mar 12, 2020
c1048c2
Raise an error when we can't load our module.
rbiro Mar 12, 2020
b8ab065
Use the provided storage for NULLs and values.
rbiro Mar 12, 2020
f823a95
Merge branch 'master' of github.com:rbiro/Multicorn
rbiro Mar 12, 2020
814569b
Force cinfos to be realloc everytime. See what that does to the cras…
rbiro Mar 12, 2020
90b4278
Remove call to spi_connect since our test doesn't need it.
rbiro Mar 12, 2020
d711326
Check for a missing relid for the foreign table.
rbiro Mar 12, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions python/multicorn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,26 @@ def commit(self):
"""
pass

def release_before(self):
"""
Hook called just before locks are released after a commit.
"""
pass

def release(self):
"""
Hook called to release locks after a commit.
"""
pass

def release_after(self):
"""
Hook called just after locks are released after a commit.
Useful if you need to mess with the database and need to
wait for locks to be released.
"""
pass

def end_scan(self):
"""
Hook called at the end of a foreign scan.
Expand Down
158 changes: 158 additions & 0 deletions python/multicorn/utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,52 @@

from logging import ERROR, INFO, DEBUG, WARNING, CRITICAL
try:
from ._utils import _log_to_postgres
from ._utils import check_interrupts
from ._utils import _execute
from ._utils import _prepare
from ._utils import _execute_stmt
from ._utils import _fetch
except ImportError as e:
# We really want to know when this happens
# for testing purposes, just ignore the output.
logging.exception("Importing utils")
from warnings import warn
warn("Not executed in a postgresql server,"
" disabling log_to_postgres", ImportWarning)

def _log_to_postgres(message, level=0, hint=None, detail=None):
pass

def _execute(sql, readonly, count):
raise Exception('Not Connected to Postgres')

def _prepare(sql, *argv):
"""
_prepare takes a sql statement and a
list of converter objects. The
converter objects define getOID
to get the postgres oid for
that argument and a getdataum method.
The getdatum method returns the
takes two a single object as
an argument (as well as self) and
returns the value of that argument
as something the sql converter
can understand. Currently this is limited
to a string, long, int, or float. So
for example dates and time stamps would
have to be passed in as strings and
the sql would have to convert them.
"""
raise Exception('Not Connected to Postgres')

def _execute_stmt(stmt, args, converters):
raise Exception('Not Connected to Postgres')

def _fetch():
raise Exception('Not Connected to Postgres')


REPORT_CODES = {
DEBUG: 0,
Expand All @@ -19,10 +56,131 @@ def _log_to_postgres(message, level=0, hint=None, detail=None):
CRITICAL: 4
}

class StatementException(Exception):
pass

def _booleanXForm(bl):
if isinstance(bl, basestring):
if bl.lower() in ('0', 'false', 'f', ''):
return 0
if bl:
return 1

return 0

class Statement(object):
type_map = None

simple_converters = {
'int8': { 'func': lambda x: long(x) },
'int4': { 'func': lambda x: int(x) },
'float8':{ 'func': lambda x: float(x) },
'text': { 'lob': True },
'bool': { 'func': _booleanXForm },
}

class Converter(object):
converter_map = {}

def __init__(self, pgtype, **kwargs):
self.pgtype = pgtype
self.lob = kwargs.get('lob', False)


def isLob(self):
return self.lob

def getdatum(self, obj):
return obj

def getOID(self):
oid = Statement.getOID(self.pgtype);
return oid

@staticmethod
def converter(*args):
def decorator(cls):
for name in args:
Converter.converter_map[name]=cls
return cls
return decorator

for c,p in simple_converters.items():
name="SimpleConverter%s" %c

d = {
'__init__': lambda s, n=c, p=p: Statement.Converter.__init__(s, n, **p)
}

if 'func' in p:
d['getdatum'] = lambda s, o, f=p['func']: f(o)

Converter.converter_map[c] = type(name, (Converter,), d)


def __init__(self, sql, pytypes):
self.converters = []
self.results = None
for t in pytypes:
if t not in Statement.Converter.converter_map:
raise StatementException("Unhandled Data Type %s" %t)
self.converters.append(Statement.Converter.converter_map[t]())
self.stmt = prepare(sql, self.converters)

def execute(self, data):
if not isinstance(data, tuple):
data = tuple(data)

ret = execute_stmt(self.stmt, data, tuple(self.converters))
self.results = fetch()
return ret

def getResults(self):
return self.results


@staticmethod
def getOID(typname):
# not thread safe.
# XXXXX FIXME, need to consider namespaces.
if Statement.type_map is None:
sql = "select typname, oid::int4 from pg_type;"
res = execute(sql)
types = fetch()
Statement.type_map = {}
for t in types:
Statement.type_map[t['typname']] = t['oid']

if typname not in Statement.type_map:
logging.error('type %s not in type_map' %typname)
raise Exception('unknown type(%s) is getoid' %typname)
return Statement.type_map.get(typname, None)




def log_to_postgres(message, level=INFO, hint=None, detail=None):
code = REPORT_CODES.get(level, None)
if code is None:
raise KeyError("Not a valid log level")
_log_to_postgres(message, code, hint=hint, detail=detail)


def execute(sql, read_only=False, count=0):
return _execute(sql, None, count)

def prepare(sql, converters):
return _prepare(sql, *converters)

def execute_stmt(plan, args, converters):
if not isinstance(args, tuple):
args = tuple( a for a in args )

if not isinstance(converters, tuple):
converters = tuple (c for c in converters );
return _execute_stmt(plan, args, converters)

def fetch():
return _fetch()


2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def get_pg_config(kind, pg_config="pg_config"):

multicorn_utils_module = Extension('multicorn._utils',
include_dirs=include_dirs,
extra_compile_args = ['-shared'],
extra_compile_args = ['-shared', '-g'],
sources=['src/utils.c'])

requires=[]
Expand Down
4 changes: 2 additions & 2 deletions src/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ reportException(PyObject *pErrType, PyObject *pErrValue, PyObject *pErrTraceback
errValue = PyString_AsString(PyObject_Str(pErrValue));
if (pErrTraceback != NULL)
{
traceback_list = PyObject_CallFunction(format_exception, "(O,O,O)", pErrType, pErrValue, pErrTraceback);
errTraceback = PyString_AsString(PyObject_CallMethod(newline, "join", "(O)", traceback_list));
traceback_list = PYOBJECT_CALLFUNCTION(format_exception, "(O,O,O)", pErrType, pErrValue, pErrTraceback);
errTraceback = PyString_AsString(PYOBJECT_CALLMETHOD(newline, "join", "(O)", traceback_list));
Py_DECREF(pErrTraceback);
Py_DECREF(traceback_list);
}
Expand Down
Loading