Re: [HACKERS] pl/python custom datatype parsers
Did any (committed?) code result from this thread ? On 11/10/2011 09:13 PM, Peter Eisentraut wrote: On tis, 2011-11-08 at 16:08 -0500, Andrew Dunstan wrote: On 03/01/2011 11:50 AM, Peter Eisentraut wrote: On fre, 2011-02-11 at 16:49 +0100, Jan Urbański wrote: I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). As I previously mentioned, I think that there should be an SQL-level way to tie together languages and types. I previously mentioned the SQL-standard command CREATE TRANSFORM as a possibility. I've had this on my PL/Python TOTHINK list for a while. Thankfully you removed all the items ahead of this one, so I'll think of something to do in 9.2. Of course we'll be able to use the actual transform code that you already wrote. Peter, Did you make any progress on this? No, but it's still somewhere on my list. I saw your blog post related to this. I think the first step would be to set up some catalog infrastructure (without DDL commands and all that overhead), and try to adapt the big case statement of an existing language to that, and then check whether that works, performance, etc. Some other concerns of the top of my head: - Arrays: Would probably not by handled by that. So this would not be able to handle, for example, switching the array handling behavior in PL/Perl to ancient compatible mode. - Range types: no idea I might work on this, but not before December, would be my guess. -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On tis, 2011-11-08 at 16:08 -0500, Andrew Dunstan wrote: On 03/01/2011 11:50 AM, Peter Eisentraut wrote: On fre, 2011-02-11 at 16:49 +0100, Jan Urbański wrote: I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). As I previously mentioned, I think that there should be an SQL-level way to tie together languages and types. I previously mentioned the SQL-standard command CREATE TRANSFORM as a possibility. I've had this on my PL/Python TOTHINK list for a while. Thankfully you removed all the items ahead of this one, so I'll think of something to do in 9.2. Of course we'll be able to use the actual transform code that you already wrote. Peter, Did you make any progress on this? No, but it's still somewhere on my list. I saw your blog post related to this. I think the first step would be to set up some catalog infrastructure (without DDL commands and all that overhead), and try to adapt the big case statement of an existing language to that, and then check whether that works, performance, etc. Some other concerns of the top of my head: - Arrays: Would probably not by handled by that. So this would not be able to handle, for example, switching the array handling behavior in PL/Perl to ancient compatible mode. - Range types: no idea I might work on this, but not before December, would be my guess. -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On 03/01/2011 11:50 AM, Peter Eisentraut wrote: On fre, 2011-02-11 at 16:49 +0100, Jan Urbański wrote: I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). As I previously mentioned, I think that there should be an SQL-level way to tie together languages and types. I previously mentioned the SQL-standard command CREATE TRANSFORM as a possibility. I've had this on my PL/Python TOTHINK list for a while. Thankfully you removed all the items ahead of this one, so I'll think of something to do in 9.2. Of course we'll be able to use the actual transform code that you already wrote. Peter, Did you make any progress on this? cheers andrew -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On fre, 2011-02-11 at 16:49 +0100, Jan Urbański wrote: I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). As I previously mentioned, I think that there should be an SQL-level way to tie together languages and types. I previously mentioned the SQL-standard command CREATE TRANSFORM as a possibility. I've had this on my PL/Python TOTHINK list for a while. Thankfully you removed all the items ahead of this one, so I'll think of something to do in 9.2. Of course we'll be able to use the actual transform code that you already wrote. -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On Sun, Feb 6, 2011 at 1:01 PM, Jan Urbański wulc...@wulczer.org wrote: That's it for now. It is an exciting feature and plpython will be the first language to think of when you're building object database if this feature is in. The design here will affect following pl/perl and other so it is important enough to discuss. Yes, I ended up writing this patch as a PoC of how you can integrate procedural languages with arbitrary addon modules, so it would be good to have a discussion about the general mechanisms. I'm aware that this discussion, and subsequently this patch, might be punted to 9.2 (although that would be a shame). It's not clear to me from this discussion whether this patch (a) now works and has consensus, and should be committed, (b) still needs more discussion, but hopes to make it into 9.1, or (c) is now 9.2 material. Can someone please clarify? -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On 11/02/11 16:43, Robert Haas wrote: On Sun, Feb 6, 2011 at 1:01 PM, Jan Urbański wulc...@wulczer.org wrote: That's it for now. It is an exciting feature and plpython will be the first language to think of when you're building object database if this feature is in. The design here will affect following pl/perl and other so it is important enough to discuss. Yes, I ended up writing this patch as a PoC of how you can integrate procedural languages with arbitrary addon modules, so it would be good to have a discussion about the general mechanisms. I'm aware that this discussion, and subsequently this patch, might be punted to 9.2 (although that would be a shame). It's not clear to me from this discussion whether this patch (a) now works and has consensus, and should be committed, (b) still needs more discussion, but hopes to make it into 9.1, or (c) is now 9.2 material. I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). Cheers, Jan -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On Fri, Feb 11, 2011 at 10:49 AM, Jan Urbański wulc...@wulczer.org wrote: On 11/02/11 16:43, Robert Haas wrote: On Sun, Feb 6, 2011 at 1:01 PM, Jan Urbański wulc...@wulczer.org wrote: That's it for now. It is an exciting feature and plpython will be the first language to think of when you're building object database if this feature is in. The design here will affect following pl/perl and other so it is important enough to discuss. Yes, I ended up writing this patch as a PoC of how you can integrate procedural languages with arbitrary addon modules, so it would be good to have a discussion about the general mechanisms. I'm aware that this discussion, and subsequently this patch, might be punted to 9.2 (although that would be a shame). It's not clear to me from this discussion whether this patch (a) now works and has consensus, and should be committed, (b) still needs more discussion, but hopes to make it into 9.1, or (c) is now 9.2 material. I believe it's (b). But as we don't have time for that discussion that late in the release cycle, I think we need to consider it identical to (c). OK, I'll mark it Returned with Feedback. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On 04/02/11 17:19, Hitoshi Harada wrote: 2011/1/28 Jan Urbański wulc...@wulczer.org: On 23/12/10 15:15, Jan Urbański wrote: Here's a patch implementing custom parsers for data types mentioned in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's an incremental patch on top of the plpython-refactor patch sent eariler. Updated to master. I reviewed this for some time today. Thank you. The patch applies with hunks, compiles and tests are passed, though it looks like not having additional test along with it. I added a simple test. I had to add an expected file for the case when hstore is compiled without PL/Python integration. - in hstore_plpython.c, PLyParsers parsers = { .in = hstore_to_dict, .out = dict_to_hstore }; I'm not sure if this coding style is used anywhere in the core. Isn't this the C99 style? Ooops, you're right. Fixed. - You need define custom variable class to use this feature. plpython.hstore = 'public.hstore'. I wonder why it's called plpython[u].hstore = 'public.hstore' (with 'u') because the language is called plpythonu. I think plpython.hstore was what showed up in discussion... I'd be fine with calling the variable plpythonu.hstore, if that's the consensus. - typo in plpython.h, Types for parsres functions that ... Fixed. - I tried the sample you mention upthread, regression=# select pick_one('a=3, b=4', 'b'); ERROR: TypeError: string indices must be integers CONTEXT: PL/Python function pick_one My python is 2.4.3 again. Hm, this means that the hstore has not been transformed into a Python dict, but into a string, which is what happens if you *don't* have plpython hstore integration enabled. I think that was because of an issue with my changes to hstore's Makefile, that made it compile without Python support, even if the sources were configured with --with-python. There's also a gotcha: if you set plpython.hstore to 'public.hstore', you will have to DROP (or CREATE OR REPLACE again) all functions that accept or return hstores, because their I/O routines are already cached. Not sure how big of a problem that is (or how to fix it in an elegant manner). Making the parameter PGC_POSTMASTER is an easy solution... but not very nice. That's it for now. It is an exciting feature and plpython will be the first language to think of when you're building object database if this feature is in. The design here will affect following pl/perl and other so it is important enough to discuss. Yes, I ended up writing this patch as a PoC of how you can integrate procedural languages with arbitrary addon modules, so it would be good to have a discussion about the general mechanisms. I'm aware that this discussion, and subsequently this patch, might be punted to 9.2 (although that would be a shame). Cheers, Jan diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 1d533fd..fd85052 100644 *** a/contrib/hstore/Makefile --- b/contrib/hstore/Makefile *** *** 1,8 # contrib/hstore/Makefile MODULE_big = hstore OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql --- 1,9 # contrib/hstore/Makefile MODULE_big = hstore + OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! hstore_plpython.o crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql *** top_builddir = ../.. *** 18,20 --- 19,28 include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif + + ifeq ($(with_python), yes) + override CFLAGS += -I$(srcdir) -I$(top_builddir)/src/pl/plpython \ + $(python_includespec) -DHSTORE_PLPYTHON_SUPPORT + SHLIB_LINK = $(python_libspec) $(python_additional_libs) \ + $(filter -lintl,$(LIBS)) $(CPPFLAGS) + endif diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out index 354fff2..049fdd5 100644 *** a/contrib/hstore/expected/hstore.out --- b/contrib/hstore/expected/hstore.out *** select count(*) from testhstore where h *** 1461,1463 --- 1461,1502 1 (1 row) + -- plpython integration + set plpython.hstore = 'public.hstore'; + create language plpythonu; + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a string + return None + $$ language plpythonu; + select coalesce(select_one(h, 'node'), 'null') as node from testhstore where select_one(h, 'line')::integer 10; + node + + AA + CBB + null + null + CBA + CBC + null + null + null + (9 rows) + + reset plpython.hstore; + create or replace function select_one(h hstore, idx text) returns text as $$ + try: + return h.get(idx) + except AttributeError: + # h has not been transformed into a dict and is a
Re: [HACKERS] pl/python custom datatype parsers
2011/1/28 Jan Urbański wulc...@wulczer.org: On 23/12/10 15:15, Jan Urbański wrote: Here's a patch implementing custom parsers for data types mentioned in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's an incremental patch on top of the plpython-refactor patch sent eariler. Updated to master. I reviewed this for some time today. The patch applies with hunks, compiles and tests are passed, though it looks like not having additional test along with it. - in hstore_plpython.c, PLyParsers parsers = { .in = hstore_to_dict, .out = dict_to_hstore }; I'm not sure if this coding style is used anywhere in the core. Isn't this the C99 style? - You need define custom variable class to use this feature. plpython.hstore = 'public.hstore'. I wonder why it's called plpython[u].hstore = 'public.hstore' (with 'u') because the language is called plpythonu. - typo in plpython.h, Types for parsres functions that ... - I tried the sample you mention upthread, regression=# select pick_one('a=3, b=4', 'b'); ERROR: TypeError: string indices must be integers CONTEXT: PL/Python function pick_one My python is 2.4.3 again. That's it for now. It is an exciting feature and plpython will be the first language to think of when you're building object database if this feature is in. The design here will affect following pl/perl and other so it is important enough to discuss. Regards, -- Hitoshi Harada -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers
Re: [HACKERS] pl/python custom datatype parsers
On 23/12/10 15:15, Jan Urbański wrote: Here's a patch implementing custom parsers for data types mentioned in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's an incremental patch on top of the plpython-refactor patch sent eariler. Updated to master. diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 1d533fd..4e6ba7b 100644 *** a/contrib/hstore/Makefile --- b/contrib/hstore/Makefile *** *** 1,8 # contrib/hstore/Makefile MODULE_big = hstore OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql --- 1,17 # contrib/hstore/Makefile MODULE_big = hstore + OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! hstore_plpython.o crc32.o ! ! ifeq ($(with_python),yes) ! ! PG_CPPFLAGS := -I$(srcdir) -I$(top_builddir)/src/pl/plpython \ ! $(python_includespec) -DHSTORE_PLPYTHON_SUPPORT ! SHLIB_LINK = $(python_libspec) $(python_additional_libs) \ ! $(filter -lintl,$(LIBS)) $(CPPFLAGS) ! endif DATA_built = hstore.sql DATA = uninstall_hstore.sql diff --git a/contrib/hstore/hstore.h b/contrib/hstore/hstore.h index 8906397..6edfc70 100644 *** a/contrib/hstore/hstore.h --- b/contrib/hstore/hstore.h *** extern Pairs *hstoreArrayToPairs(ArrayTy *** 174,179 --- 174,182 #define HStoreExistsAllStrategyNumber 11 #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ + /* PL/Python support */ + extern void hstore_plpython_init(void); + /* * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; * for now, we default to on for the benefit of people restoring old dumps diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 0d6f0b6..92c8db9 100644 *** a/contrib/hstore/hstore_io.c --- b/contrib/hstore/hstore_io.c *** PG_MODULE_MAGIC; *** 20,25 --- 20,26 /* old names for C functions */ HSTORE_POLLUTE(hstore_from_text, tconvert); + void _PG_init(void); typedef struct { *** hstore_send(PG_FUNCTION_ARGS) *** 1211,1213 --- 1212,1220 PG_RETURN_BYTEA_P(pq_endtypsend(buf)); } + + void + _PG_init(void) + { + hstore_plpython_init(); + } diff --git a/contrib/hstore/hstore_plpython.c b/contrib/hstore/hstore_plpython.c index ...4ba111a . *** a/contrib/hstore/hstore_plpython.c --- b/contrib/hstore/hstore_plpython.c *** *** 0 --- 1,251 + /* + * contrib/src/hstore_plpython.c + * + * bidirectional transformation between hstores and Python dictionary objects + */ + + /* Only build if PL/Python support is needed */ + #if defined(HSTORE_PLPYTHON_SUPPORT) + + #if defined(_MSC_VER) defined(_DEBUG) + /* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ + #undef _DEBUG + /* Also hide away errcode, since we load Python.h before postgres.h */ + #define errcode __msvc_errcode + #include Python.h + #undef errcode + #define _DEBUG + #elif defined (_MSC_VER) + #define errcode __msvc_errcode + #include Python.h + #undef errcode + #else + #include Python.h + #endif + + #include postgres.h + #include utils/guc.h + #include utils/builtins.h + #include utils/syscache.h + #include catalog/namespace.h + + #include plpython.h + #include hstore.h + + static Oid get_hstore_oid(const char *name); + static void set_hstore_parsers(Oid); + + static PyObject *hstore_to_dict(void *, Datum); + static Datum dict_to_hstore(void *, int32, PyObject *); + + /* GUC variables */ + + static char *hstore_name; + + /* Previous hstore OID */ + + static Oid previous; + + PLyParsers parsers = { + .in = hstore_to_dict, + .out = dict_to_hstore + }; + + static PyObject * + hstore_to_dict(void *ignored, Datum d) + { + HStore *hstore = DatumGetHStoreP(d); + char*base; + HEntry *entries; + int count; + int i; + PyObject*ret; + + base = STRPTR(hstore); + entries = ARRPTR(hstore); + + ret = PyDict_New(); + + count = HS_COUNT(hstore); + + for (i = 0; i count; i++) + { + PyObject *key, *val; + + key = PyString_FromStringAndSize(HS_KEY(entries, base, i), + HS_KEYLEN(entries, i)); + if (HS_VALISNULL(entries, i)) { + Py_INCREF(Py_None); + val = Py_None; + } + else { + val = PyString_FromStringAndSize(HS_VAL(entries, base, i), + HS_VALLEN(entries, i)); + } + + PyDict_SetItem(ret, key, val); + } + + return ret; + } + + static Datum + dict_to_hstore(void *ignored, int32 typmod, PyObject *dict) + { + HStore *hstore; + int pcount; + Pairs *pairs; + PyObject *key; + PyObject *value; + Py_ssize_t pos; + char
[HACKERS] pl/python custom datatype parsers
Here's a patch implementing custom parsers for data types mentioned in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's an incremental patch on top of the plpython-refactor patch sent eariler. Git branch for this patch: https://github.com/wulczer/postgres/tree/custom-parsers. The idea has been discussed in http://archives.postgresql.org/pgsql-hackers/2010-12/msg01307.php. With that patch, when built with --with-python, the hstore module includes code that adds a GUC called plpython.hstore. This GUC should be set to the full name of the hstore datatype, for instance plpython.hstore = 'public.hstore'. If it is set, the datatype's OID is looked up and hstore sets up a rendezvous variable called PLPYTHON_OID_PARSERS that points to two functions that can convert a hstore Datum to a PyObject and back. PL/Python ot the other hand when it sees an argument with an unknown type tries to look up a rendezvous variable using the type's OID and if it finds it, it uses the parser functions pointed at by that variable. Long story short, it works so: LOAD 'hstore'; SET plpython.hstore = 'public.hstore' CREATE FUNCTION pick_one(h hstore, key text) RETURNS hstore AS $$ return {key: h[key]} $$ LANGUAGE plpythonu; SELECT pick_one('a=3,b=4', 'b') -- gives bask a hstore 'b=4' There's some ugliness with how hstore's Makefile handles building it, and I'm not sure what's needed to make it work with the Windows build system. Also, documentation is missing. It's already usable, but if we decide to commit that, I'll probably need some help with Windows and docs. I first tried to make hstore generate a separate .so with that functionality if --with-python was specified, but couldn't convince the Makefile to do that. So if you configure the tree with --with-python, hstore will link to libpython, maybe that's OK? Cheers, Jan PS: of course, once committed we can add custom parsers for isbn, citext, uuids, cubes, and other weird things. J diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index e466b6f..dbeeb89 100644 *** a/contrib/hstore/Makefile --- b/contrib/hstore/Makefile *** top_builddir = ../.. *** 5,12 include $(top_builddir)/src/Makefile.global MODULE_big = hstore OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql --- 5,21 include $(top_builddir)/src/Makefile.global MODULE_big = hstore + OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! hstore_plpython.o crc32.o ! ! ifeq ($(with_python),yes) ! ! PG_CPPFLAGS := -I$(srcdir) -I$(top_builddir)/src/pl/plpython \ ! $(python_includespec) -DHSTORE_PLPYTHON_SUPPORT ! SHLIB_LINK = $(python_libspec) $(python_additional_libs) \ ! $(filter -lintl,$(LIBS)) $(CPPFLAGS) ! endif DATA_built = hstore.sql DATA = uninstall_hstore.sql diff --git a/contrib/hstore/hstore.h b/contrib/hstore/hstore.h index 8906397..6edfc70 100644 *** a/contrib/hstore/hstore.h --- b/contrib/hstore/hstore.h *** extern Pairs *hstoreArrayToPairs(ArrayTy *** 174,179 --- 174,182 #define HStoreExistsAllStrategyNumber 11 #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ + /* PL/Python support */ + extern void hstore_plpython_init(void); + /* * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; * for now, we default to on for the benefit of people restoring old dumps diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 0d6f0b6..92c8db9 100644 *** a/contrib/hstore/hstore_io.c --- b/contrib/hstore/hstore_io.c *** PG_MODULE_MAGIC; *** 20,25 --- 20,26 /* old names for C functions */ HSTORE_POLLUTE(hstore_from_text, tconvert); + void _PG_init(void); typedef struct { *** hstore_send(PG_FUNCTION_ARGS) *** 1211,1213 --- 1212,1220 PG_RETURN_BYTEA_P(pq_endtypsend(buf)); } + + void + _PG_init(void) + { + hstore_plpython_init(); + } diff --git a/contrib/hstore/hstore_plpython.c b/contrib/hstore/hstore_plpython.c index ...081a33e . *** a/contrib/hstore/hstore_plpython.c --- b/contrib/hstore/hstore_plpython.c *** *** 0 --- 1,249 + /* + * contrib/src/hstore_plpython.c + * + * bidirectional transformation between hstores and Python dictionary objects + */ + + /* Only build if PL/Python support is needed */ + #if defined(HSTORE_PLPYTHON_SUPPORT) + + #if defined(_MSC_VER) defined(_DEBUG) + /* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ + #undef _DEBUG + /* Also hide away errcode, since we load Python.h before postgres.h */ + #define errcode __msvc_errcode + #include Python.h + #undef errcode + #define _DEBUG + #elif defined (_MSC_VER) + #define errcode __msvc_errcode + #include Python.h + #undef errcode + #else + #include Python.h + #endif + + #include postgres.h + #include