changeset 0:b32ff9c84370

Initial version, copied from changeset bb36451050ad of MonetDB.
author Sjoerd Mullender <sjoerd@acm.org>
date Wed, 14 Sep 2016 15:12:34 +0200 (2016-09-14)
parents
children e6d9dd0cff68
files INSTALL Makefile MonetDB-client-php.spec README TODO doc/generate_doc.sh examples/query.php examples/simple_query.php lib/php_mapi.inc lib/php_monetdb.php license.txt
diffstat 10 files changed, 1621 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,59 @@
+= System requirements =
+ * PHP5 with sockets support compiled and enabled;
+ * MonetDB5 / Merovingian using mapi protocol version >= 8.
+
+= Enable socket support in PHP =
+
+In order to get the native monetdb PHP implementation to work you need
+a PHP5 interpreter with socket support compiled and enabled.
+
+You can verify wether your interpreter has sockets support compiled by running the command:
+
+$ php-config --configure-options
+
+and check for the presence of the "--enable-sockets" option.
+
+In order to enable sockets support in your php interpreter; 
+load the php_sockets.so library from the php.ini configuration file.
+
+
+;;;;;;;;;;;;;;;;;;;;;;
+; Dynamic Extensions ;
+;;;;;;;;;;;;;;;;;;;;;;
+;
+
+extension=php_sockets.so
+
+
+= Set a timezone =
+In order to avoid unexpected behaviours with the library it is advised to set a
+timezone for the PHP interpreter
+
+This can be done by enabling the setting "date.timezone" in the php.ini configuration file.
+
+[Date]
+; Defines the default timezone used by the date functions
+date.timezone = Europe/Amsterdam
+
+For a list of supported timezone see: http://php.net/manual/en/timezones.php
+
+= Use the library =
+The library is shipped as a series of include files and does not come as a binary object (.so).
+
+In order to use it in your applications include the "lib/php_monetdb.php" file
+in all pages that want to make use of a database connection. Note that this file requires php_mapi.inc
+to be present in the same directory.
+
+If you installed MonetDB (either by building from sources or using binary packages) the native PHP interface
+will be located in "/usr/share/php/monetdb"
+
+On most system you should be able to use the library by including or requiring "monedtdb/php_monetdb.php".
+
+In case the system is not able to find the library it is possible to specify the include path in php.ini
+
+; UNIX: "/path1:/path2"
+include_path = ".:/usr/share/php/monetdb"
+
+Please note that the library is not installed by the Windows installer. When building from source on this platform the includes will be installed under the given prefix.
+
+
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+datadir = /usr/share
+
+all:
+	@echo "Nothing to do"
+
+install:
+	mkdir -p $(DESTDIR)$(datadir)/php/monetdb
+	cp lib/php_mapi.inc lib/php_monetdb.php $(DESTDIR)$(datadir)/php/monetdb
new file mode 100644
--- /dev/null
+++ b/MonetDB-client-php.spec
@@ -0,0 +1,47 @@
+Name:		MonetDB-client-php
+Version:	11.25.1
+Release:	1%{?dist}
+Summary:	MonetDB php interface
+Group: Applications/Databases
+
+License:	MPLv2.0
+URL:		http://www.monetdb.org/
+Source0:	http://dev.monetdb.org/downloads/php/monetdb-php-%{version}.tar.bz2
+
+Requires:	php
+BuildArch:	noarch
+
+%description
+MonetDB is a database management system that is developed from a
+main-memory perspective with use of a fully decomposed storage model,
+automatic index management, extensibility of data types and search
+accelerators.  It also has an SQL frontend.
+
+This package contains the files needed to use MonetDB from a PHP
+program.
+
+
+%prep
+%autosetup -n monetdb-php-%{version}
+
+
+%build
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+%make_install
+
+
+%files
+%license license.txt
+%defattr(-,root,root)
+%dir %{_datadir}/php/monetdb
+%{_datadir}/php/monetdb/*
+
+
+
+%changelog
+* Wed Sep 14 2016 Sjoerd Mullender <sjoerd@acm.org>
+- Initial independent version.
+
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,20 @@
+= Native PHP implementation of the MonetDB interface. =
+
+The php API aims at being compatible with the one provided by the C implementation.
+
+== Directory structure ==
+ - lib/
+       php_mapi.inc - mapi protocol implementation
+       php_monetdb.php - php api
+
+ - examples/simple_query.php - an example of php+monetdb in action
+
+ - INSTALL - note about installing and using the native interface
+
+ - TODO: list of pending tasks.
+
+== Benchmark ==
+
+A comparison between cimpl and native can be found at: http://www.ins.cwi.nl/~gm/bench/
+
+php_test.tar.bz2 - contains an utility to measure the performance of the two php APIS.	
new file mode 100644
new file mode 100755
--- /dev/null
+++ b/doc/generate_doc.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+#
+#
+# Generates (html) documentation from source code comments.
+#
+# Requires: phpdoc (http://www.phpdoc.org/)
+#
+# Usage: generate_doc.sh <source dir> <doc dir>
+#
+# By default <doc dir> is located within the source tree 'doc/' directory
+#
+
+source_code=$1
+
+current_dir=$(pwd)
+doc_dir=$current_dir/$2
+
+title="MonetDB5 PHP API"
+
+phpdoc -ti $title -o "HTML:frames:phpedit" -f $source_code -t $doc_dir
new file mode 100644
--- /dev/null
+++ b/examples/query.php
@@ -0,0 +1,44 @@
+<?php
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+?>
+
+<?php
+
+/**
+* Connect to a database and perform a query.
+*/
+
+require '../lib/php_monetdb.php';
+
+define("DB", "php_demo");
+
+/* Establish a connection and report errors in case they occour */
+$db = monetdb_connect($lang = "sql", $host = "127.0.0.1", $port = "50000", $database = DB , $username = "monetdb", $password = "monetdb" ) or trigger_error(monetdb_last_error());
+
+/* Fire a query */
+
+$query = "SELECT * FROM TABLES, TABLES";
+$res = monetdb_query($db, monetdb_escape_string($query)) or trigger_error(monetdb_last_error());
+
+/* Print the number of rows in the result set */
+print "Rows: " . monetdb_num_rows($res) . "\n";
+
+/* Iterate over the result set returning rows as objects */
+//while ( $row = monetdb_fetch_object($res) )
+//{
+//	print_r($row);
+//}
+
+/* Free the result set */
+monetdb_free_result($res);
+
+/* Disconnect from the database */
+if (monetdb_connected($db)) {
+	monetdb_disconnect($db);
+}
+
+?>
new file mode 100644
--- /dev/null
+++ b/examples/simple_query.php
@@ -0,0 +1,49 @@
+<?php
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"" xml:lang="en" lang="en">
+
+<head>
+	<title>MonetDB Query</title>
+</head>
+
+<body>
+
+<?php
+	require '../lib/php_monetdb.php';
+	if ( isset($_POST['query']) )
+	{
+		$db = monetdb_connect($lang = "sql", $host = "127.0.0.1", $port = "50000" , $username = "monetdb", $password = "monetdb", $database = "php_demo" )
+		or die(monetdb_last_error());
+		
+		$sql = monetdb_escape_string($_POST['query']);
+		$res = monetdb_query($sql);
+		while ( $row = monetdb_fetch_assoc($res) )
+		{
+			print "<pre>\n";
+			print_r($row);
+			print "</pre>\n";
+		}
+		
+		monetdb_disconnect();
+	}
+
+	print "<form method=\"post\" action=\"{$_SERVER['PHP_SELF']}\">\n";
+	print "<label for=\"query\">SQL Query:</label>\n";
+	print "<input type=\"text\" name=\"query\" id=\"query\"
+	value=\"{$_POST['query']}\" />\n";
+	print "<input type=\"submit\" value=\"Execute\" />\n";
+	print "</form>\n";
+?>	
+
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/lib/php_mapi.inc
@@ -0,0 +1,825 @@
+<?php
+/**
+   *	Implementation of the MAPI protocol (v9).
+   *
+   *
+   * 	Provides:
+   * -  mapi_query($data) {
+   * -  mapi_store($data) {
+   * -  php_parse_tuples($rows) {
+   * -  mapi_connect() {
+   * -  mapi_authenticate($user, $passwd, $hash, $salt, $dbname) {
+   * -  mapi_read() {
+   * -  mapi_write($msg) {
+   * -  set_timezone() {
+   * -  format_command($cmd) {
+   * -  mapi_connect_proxy() {
+   * -  mapi_open() {
+   * - mapi_close() {
+   *
+**/
+
+	define("MAX_PACKET_SIZE", 8190); // Maximum packet size
+
+	define("REPLY_SIZE", "250"); // Set the initial reply size
+
+	define("Q_TABLE", "1"); // SELECT operation
+	define("Q_UPDATE", "2"); // INSERT/UPDATE operations
+	define("Q_CREATE", "3"); // CREATE/DROP TABLE operations
+	define("Q_TRANSACTION", "4"); // TRANSACTION
+	define("Q_PREPARE", "5");
+	define("Q_BLOCK", "6"); // QBLOCK message
+
+	define("MSG_REDIRECT", "^"); // auth redirection through merovingian
+	define("MSG_QUERY", "&");
+	define("MSG_SCHEMA_HEADER", "%");
+	define("MSG_INFO", "!"); // info response from mserver
+	define("MSG_TUPLE", "[");
+	define("MSG_PROMPT", "");
+
+	define("PROTOCOL_v9", 9); // supported protocol(s)
+
+	define("LANG_SQL", "sql");
+
+	define("MEROVINGIAN", "merovingian");
+	define("MSERVER", "mserver");
+	define("MONETDB", "monetdb");
+
+	define('MAX_MEROVINGIAN_ITER', 10); // Maximum number of iterations during proxied auth through merovingian
+
+	define('PLACEHOLDER', '?'); // Used for string escaping.
+
+	/**
+	* $connection_pool contains a list of the active database connections
+	*/
+	$connection_pool = array();
+
+	/**
+	* Stores the last error reported by the server
+	*/
+	$last_error = "";
+
+
+	/**
+	* Execute an SQL query and return the resulting handle by reference.
+	*/
+	function mapi_execute($conn=NULL, $query) {
+		global $connection_pool;
+		global $rows;
+		/**
+		* Query handle
+		*
+		* "conn" - id of the connection that fired the query
+		* "header" - table header
+		* "record_set" - retrieved record set (if present)
+		* "operation" - query type
+		*
+		*/
+
+		// if multiple connections are present, require the user to specify which one to use.
+		if ($conn == NULL) {
+			return FALSE;
+		} else if (($socket = $conn["socket"]) == NULL) {
+			return FALSE;
+		}
+
+		/* Fire the query and read back the response */
+		$buf = mapi_write($socket, format_query($query, $conn["lang"]));
+		$data = mapi_read($socket);
+
+		if ($conn['lang'] == LANG_SQL) {
+			$handle = array("conn" => "", "header" => array(), "query" => "", "record_set" => array(), "operation" => -1, "last_row" => 0);
+			$handle["conn"] = $conn["id"];
+
+			if ( ($operation = mapi_store($data, $handle)) == FALSE) {
+				return FALSE;
+			}
+
+			/* The query produced a record set */
+			if ($operation == Q_TABLE || $operation == Q_BLOCK) {
+				// fetch the whole result set
+				if ($handle["query"]["index"] < $handle["query"]["rows"]) {
+					mapi_fetch_next($conn, $handle);
+				}
+			}
+
+			if ($handle == NULL) {
+				return FALSE;
+			}
+
+			return $handle;
+		}
+	}
+
+	function mapi_fetch_next($conn, &$handle){
+		if ($conn['socket'] == NULL){
+			return FALSE;
+		}
+		$socket = $conn['socket'];
+
+		$offset = 10;
+		while ($handle["query"]["index"] < $handle["query"]["rows"]) {
+			// export a new window
+			$left_rows = $handle["query"]["rows"] - $handle["query"]["index"];
+
+			$exp_size = min($offset, $left_rows);
+			$offset += 100;
+			mapi_write($socket, format_command("export " . $handle["query"]["id"] . " " . $handle["query"]["index"] . " " . $exp_size));
+			$data = mapi_read($socket);
+
+			if ( ($operation = mapi_store($data, $handle)) == FALSE ) {
+				return FALSE;
+			}
+
+			$handle["query"]["index"] += $exp_size;
+			$handle["operation"] = $operation;
+		}
+
+		return $handle;
+	}
+
+	function mapi_store($data, &$handle) {
+		global $last_error;
+		// global $rows;
+
+		$data = explode("\n", $data);
+
+		$operation = "";
+		$header = ""; // store temporary header informations;
+
+		$rows = ""; // stores (partially) retrieved rows
+		foreach ($data as $row) {
+			/*
+			 	PHP5.2 complains when $row[0] is accessed with:
+
+				Notice: Uninitialized string offset: 0
+
+				In order to avoid the E_NOTICE error substr($row, 0, 1) is used
+				to access the first character of a string
+
+			*/
+			if (substr($row, 0, 1) == MSG_QUERY) {
+				if ($row[1] == Q_TABLE) {
+					$operation = Q_TABLE;
+					// save info about the query
+					$fields = explode(" ", $row);
+					$handle["query"] = array("id" => $fields[1], "rows" => $fields[2], "cols" => $fields[3], "index" => $fields[4]);
+
+				} else if ($row[1] == Q_UPDATE) {
+					$operation = Q_UPDATE;
+					$fields = explode(" ", $row);
+					$handle["query"] = array("affected" => $fields[1]);
+				} else if ($row[1] == Q_CREATE) {
+					$operation = Q_CREATE;
+				} else if ($row[1] == Q_TRANSACTION) {
+					$operation = Q_TRANSACTION;
+				} else if ($row[1] == Q_BLOCK) {
+					// add Q_BLOCK to the record set
+					$operation = Q_BLOCK;
+				}
+			} else if (substr($row, 0, 1) == MSG_SCHEMA_HEADER){
+				// process the table header
+				$header = $header . $row . "\n";
+			} else if (substr($row, 0, 1) == MSG_TUPLE) {
+				// process tuples
+				$rows .= $row;
+			} else if (substr($row, 0, 1) == MSG_PROMPT) {
+				fast_array_merge($handle["record_set"], php_parse_tuples($rows));
+			} else if (substr($row, 0, 1) == MSG_INFO) {
+				$last_error = $row;
+				return FALSE;
+			}
+		}
+		/*
+		if ($record_set != "") {
+			//$handle["record_set"] = array_merge($handle["record_set"], $record_set);
+			$handle["record_set"] = $handle["record_set"] . $record_set;
+		}*/
+
+		if ($header != "") {
+
+			$handle["header"] = php_parse_header($header);
+			/* Store the number of fields returned by the dataset */
+		   	if ($operation == Q_TABLE || $operation == Q_BLOCK ) {
+				$handle["query"]["fields"] = count($handle["header"]["fields"]);
+			}
+		}
+
+		$handle["operation"] = $operation;
+
+		return $operation;
+	}
+
+	/* Concatenates two arrays in place */
+	function fast_array_merge(&$a1, $a2) {
+		foreach ($a2 as $row) {
+			$a1[] = $row;
+		}
+	}
+
+	function php_parse_tuples($rows) {
+		//$parsed_rows = "";
+
+		$rows = rtrim($rows, "\t]");
+		return explode("\t]", $rows);
+//		print_r($parsed_rows);
+		/*
+		foreach ($rows as &$row) {
+
+			$row = ltrim($row, "[ ");
+			$row = explode(",\t",  $row);
+
+			foreach ($row as &$field) {
+				$field = stripslashes($field);
+				// strip left/right \" chars and right ','
+				$field = rtrim($field, '"');
+				$field = ltrim($field, '"');
+			}
+			$parsed_rows[] = $row;
+		}
+		*/
+	//	return $parsed_rows;
+	}
+
+	function php_parse_row($row) {
+		$row = ltrim($row, "[ ");
+		$row = explode(",\t",  $row);
+		foreach ($row as &$field) {
+			if ($field == "NULL") {
+				$field = NULL;
+			}
+			else {
+
+				$field = stripslashes($field);
+				// strip left/right \" chars and right ','
+				$field = rtrim($field, '"');
+				$field = ltrim($field, '"');
+			}
+		}
+		return $row;
+	}
+
+
+	function php_parse_header($header) {
+		$header = explode("\n", $header);
+		$name = $header[0];
+
+		$header_array = array();
+
+		/* Field names */
+		$header[1] = ltrim($header[1], "% ");
+		$header[1] = explode("#", $header[1]);
+		$header_array["fields"] = explode(",\t", $header[1][0]);
+
+		/* Field types */
+		$header[2] = ltrim($header[2], "% ");
+		$header[2] = explode("#", $header[2]);
+		$header_array["types"] = explode(",\t", $header[2][0]);
+
+		return $header_array;
+	}
+
+	/*
+		$conn_opts is an array containing:
+		username =>
+		password =>
+		host 	 =>
+		port 	 =>
+		database =>
+		hash	 =>
+
+	*/
+	function mapi_connect(&$socket, &$options, $merovingian_iter=NULL) {
+		global $last_error;
+		global $connection_pool;
+
+		$host 	= $options["host"];
+		$port 	= $options["port"];
+
+		$user 	= $options["username"];
+		$passwd = $options["password"];
+		$hash 	= $options["hashfunc"];
+		$dbname = $options["database"];
+
+		$lang 	= $options["lang"];
+
+
+		/* No merovingian redirect. Perform an actual connection. */
+		if ($merovingian_iter == NULL) {
+			if (socket_connect($socket, $host, $port) == FALSE) {
+            	$last_error = socket_strerror(socket_last_error());
+              	throw new Exception('Cannot connect to server: ' . $last_error);
+            }
+		}
+
+
+		// get server challenge
+		$challenge = mapi_read($socket);
+
+		/*
+			Array
+			(
+			    [0] => void
+			    [1] => merovingian
+			    [2] => 8
+			    [3] => plain
+			    [4] => LIT
+			)
+
+		*/
+		$credentials = explode(":", $challenge);
+		$algos = explode(',', $credentials[3]);
+
+
+		// $challenge[0] contains the salt
+		if ($credentials[2] == PROTOCOL_v9) {
+			// $credentials[5] contains pwhash
+			// $credentials[0] contains the hash
+			mapi_authenticate_v9($socket, $user, $passwd, $hash, $algos, $credentials[0], $dbname, $credentials[5]);
+		} else {
+			$last_error = "Protocol " . $credentials[2] . " not supported. Aborting.";
+			return FALSE;
+		}
+
+		$response = mapi_read($socket);
+
+		/* Follow the first redirect */
+
+		if ($response != MSG_PROMPT) {
+		// not ready to authenticate yet
+			if ($response[0] == MSG_REDIRECT) {
+				$redirects = explode("\n", $response);
+	                 	/* Follow the first redirect */
+
+				if ( ($redirects[0] == "") || (substr($redirects[0], 0, 6) != "^mapi:") ) {
+					print "Invalid redirect " . $redirects[0] . "\n";
+					return FALSE;
+				}
+
+				$link = substr($redirects[0], 6, strlen($redirects[0]));
+				$redirect_to = parse_url($link);
+				//print_r($redirect_to);
+				if ($redirect_to['scheme'] == MEROVINGIAN) {
+					if ($merovingian_iter < MAX_MEROVINGIAN_ITER) {
+						$merovingian_iter++;
+						if (mapi_connect($socket, $options, $merovingian_iter) == FALSE) {
+							return FALSE;
+						}
+					} else {
+						$last_error = "Maximum number of merovingian iterations exceeded during authentication\n";
+						return FALSE;
+					}
+				}
+			 	else if($redirect_to['scheme'] == MONETDB ) {
+
+					$options['host'] = $redirect_to['host'];
+					$options['port']     = $redirect_to['port'];
+					$options['database'] = ltrim($redirect_to['path'], '/');
+
+					socket_close($socket);
+					$socket = mapi_open();
+					if (mapi_connect($socket, $options) == FALSE) {
+						return FALSE;
+					}
+
+			    } else {
+					print $response;
+					return FALSE;
+				}
+
+			} else if ($response[0] == MSG_INFO) {
+				$last_error = $response;
+				return FALSE;
+			}
+		}
+
+		return TRUE;
+	}
+
+	/* Hash function names have to be uppercase */
+	function mapi_authenticate_v9($socket, $user, $passwd, $hash, $algos, $salt, $dbname, $pwhash) {
+		$auth_string = "";
+		if ( (is_array($algos) && (! in_array(strtoupper($hash), $algos)) )  ) 			 {
+			$last_error = "Hash function " . $hash . " not supported";
+			return FALSE;
+		}
+
+		$pwhashsum = hash(strtolower($pwhash), $passwd);
+		$hashsum   = hash(strtolower($hash), $pwhashsum . $salt);
+
+		$auth_string = "BIG:" . $user . ":{" . strtoupper($hash) . "}" . $hashsum . ":sql:" . $dbname . ":";
+
+		mapi_write($socket, $auth_string);
+	}
+
+	// decode the header and get the requested amount of data
+	function mapi_read($socket=NULL) {
+		# get the first 2 bytes
+		if ( ($header = socket_read($socket, 2)) == FALSE) {
+			$last_error = socket_strerror(socket_last_error());
+			throw new Exception('Lost connection with the server: ' . $last_error);
+		}
+		$data = "";
+
+		$chunk_size = ((ord($header[1]) << 7) | (ord($header[0]) >> 1));
+		// keep reading until we have everything
+		while (strlen($data) < $chunk_size) {
+			$data .= socket_read($socket, $chunk_size - strlen($data));
+		}
+
+
+		while ((ord($header[0]) & 1) == 0 ) {
+			if ( ($header = socket_read($socket, 2)) == FALSE) {
+				$last_error = socket_strerror(socket_last_error());
+				throw new Exception('Lost connection with the server: ' . $last_error);
+			}
+
+			$chunk_size = ( ((ord($header[1]))  << 7) | (ord($header[0]) >> 1) );
+
+			$block = "";
+			while (strlen($block) < $chunk_size) {
+				if ( ($block .= socket_read($socket, $chunk_size - strlen($block))) == FALSE) {
+					$last_error = socket_strerror(socket_last_error());
+					throw new Exception('Lost connection with the server: ' . $last_error);
+				}
+			}
+
+
+			$data = $data . $block;
+		}
+		if (strlen($data) == 0) {
+			return "";
+		}
+
+		return $data;
+	}
+
+
+	// encode data and send it to the server. Returns the number of bytes sent.
+	function mapi_write($socket=NULL, $msg) {
+		// print "Msg_len: " . strlen($msg) . "\n";
+		global $last_error;
+
+		$fb = 0;
+		$sb = 0;
+
+		$pos = 0;
+		$data = "";
+
+		$buf = 0;
+
+		$is_final = FALSE;
+		while (! $is_final) {
+			$data  = substr($msg, $pos, min(MAX_PACKET_SIZE, (strlen($msg) - $pos))  );
+			$pos += strlen($data);
+
+			$end = 0; // more packets will follow
+			if ( (strlen($msg) - $pos) == 0) {
+				$is_final = TRUE;
+				$end = 1;
+			}
+
+			$fb = (strlen($data) << 1) | $end;
+
+			/**
+			  * socket_write() does not guarantee all data to be transmitted.
+			  * Make sure that the buffer is flushed.
+			*/
+
+			if ( ($buf = socket_flush($socket, pack("v", $fb) . $data)) == FALSE) {
+				$last_error = socket_strerror(socket_last_error());
+				return -1;
+			}
+
+		}
+		return $buf;
+	}
+
+	function set_timezone($socket=NULL) {
+		global $last_error;
+
+		$tz_offset = "'" . date('P') . "'"; /* Difference to Greenwich time (GMT) with colon between hours and minutes */
+
+		$query = "SET TIME ZONE INTERVAL " . $tz_offset . " HOUR TO MINUTE";
+
+		$buf = mapi_write($socket, format_query($query, LANG_SQL)); // set_timezone is called only when connecting to an sql db
+
+		$response = mapi_read($socket);
+
+		if ($response == "") {
+			return TRUE;
+		} else {
+
+			$last_error = $response;
+			return $response;
+		}
+	}
+
+	function format_command($cmd) {
+		return "X" . $cmd;
+	}
+
+
+	function mapi_connect_proxy(&$options) {
+		global $last_error;
+		global $connection_pool;
+		global $pconnect_count;
+
+		$merovingian_iter = 0;
+
+		/**
+		  * When connecting, the function would first try to find a (persistent) link that's already
+		  * open with the same host, username and password. If one is found, an identifier for it will be returned
+		  * instead of opening a new connection.
+		  * TODO: move this check to mapi_connect() to deal with options arrays rewritten by redirects.
+		*/
+		if ($options['persistent'] == TRUE) {
+			if (count($connection_pool) > 0) {
+				foreach ($connection_pool as $conn) {
+					if ( ($conn["persistent"] == TRUE) && ($conn["dbname"] == $options['database']) && ($conn['username'] == $options['username']) && ($conn["password"] == hash($options['hashfunc'], $options['password']) && ($conn['host']) == $options['host']) && ( $conn['port'] == $options['port'] ) ) {
+						return $conn;
+					}
+				}
+			}
+		}
+
+		$socket	= mapi_open();
+
+		if ( mapi_connect($socket, $options, $merovingian_iter) == TRUE ) {
+			/* Connected */
+
+			// Create a new connection instance and insert an entry in the connections table
+			$id = mapi_generate_id();
+			if ($options['lang'] == LANG_SQL) {
+
+
+				/*
+					PHP requires a timezone to be specified in the configuration environment; if not specified
+					either in php.ini or via date_default_timezone_set(), a default is set 'Europe/Berlin'.
+
+					PHP complains in case we query the OS to get system's timezone (E_STRICT error).
+
+					To avoid unexpected behaviours and warnings at execution time we set a timezone on mserver
+					only if PHP interpreter is aware of it (php.ini contains a date.timezone entry).
+				*/
+				if (ini_get("date.timezone")) {
+					set_timezone($socket); // set the timezone according to the system's configuration
+				}
+
+				// export the reply size (max number of tuples returned at query executions)
+				mapi_write($socket, format_command("reply_size " . REPLY_SIZE));
+				if (strlen($response = mapi_read($socket)) > 0 ) {
+					// something went wrong
+					$last_error = $response;
+					return FALSE;
+				}
+			} else {
+				return FALSE;
+			}
+		} else {
+			socket_close($socket);
+			return FALSE;
+		}
+
+		$connection = array("id" => $id, "socket" => $socket, "host" => $options["host"], "port" => $options["port"], "dbname" => $options['database'], "username" => $options["username"], "password" => hash($options['hashfunc'], $options["password"]), "transactions" => array(), "persistent" => $options["persistent"], "lang" => $options['lang']);
+
+		$connection_pool[] = $connection;
+
+		return $connection;
+	}
+
+	function mapi_connected($conn=NULL) {
+		global $last_error;
+		global $connection_pool;
+
+		if ($conn == NULL) {
+			return FALSE;
+		} else {
+			return mapi_ping($conn);
+		}
+
+		return FALSE;
+	}
+
+	function mapi_ping($conn=NULL) {
+		if ($conn != NULL) {
+			switch($conn['lang']) {
+				case LANG_SQL:
+					$res = mapi_execute($conn, "select true;");
+					break;
+			}
+			if ($res != NULL) {
+				if (!is_array($res['query']) ||
+					!array_key_exists('id', $res['query']) ||
+					mapi_free_result($conn['id'], $res['query']['id']))
+			   	{
+					return TRUE;
+				}
+			}
+		}
+		return FALSE;
+	}
+
+	/* Returns a pointer to the current (last initialized) connection */
+	function mapi_get_current_conn() {
+		global $connection_pool;
+
+		if (count($connection_pool) > 0) {
+			return end($connection_pool);
+		} else {
+			return FALSE;
+		}
+	}
+
+	/* Returns a socket */
+	function mapi_open() {
+		$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+		if ($socket == FALSE) {
+			$last_error = socket_strerror(socket_last_error());
+			return FALSE;
+		}
+
+		return $socket;
+	}
+
+	function mapi_close($conn=NULL) {
+		global $connection_pool;
+
+		/* TODO: start closing connection at the end of the array! */
+		if ($conn == NULL) {
+			if ((count($connection_pool) == 1) && ($connection_pool[0]["persistent"]) == FALSE) {
+				$socket = $connection_pool[0]["socket"];
+				socket_close($socket);
+
+				foreach ($connection_pool as $field) {
+					if (isset($field)) {
+						unset($field);
+					}
+				}
+				unset($connection_pool);
+
+				return TRUE;
+			}
+
+		} else {
+			$socket = $conn["socket"];
+			socket_close($socket);
+
+			/* remove the $conn from the pool */
+			// Create anonymous callback function to filter results for connection.
+			$function_body = 'return ( $input[\'id\'] != "'.$conn["id"].'" );';
+			$function_name = create_function('$input', $function_body);
+			// Filter the results array using the anonymous callback function.
+			$connection_pool = array_filter($connection_pool, $function_name);
+
+			if (isset($conn)) {
+				foreach ($conn as $field) {
+					if (isset($field)) {
+						unset($field);
+					}
+				}
+				unset($conn);
+			}
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	function mapi_free_result($conn_id, $res_id) {
+		global $connection_pool;
+		global $last_error;
+		/* Fetch the connection from the pool */
+
+		$conn = NULL;
+		foreach ($connection_pool as $connection) {
+			if ($connection["id"] == $conn_id) {
+				$conn = $connection;
+				break;
+			}
+		}
+
+		if ($conn == NULL) {
+			return FALSE;
+		}
+
+		$socket = $conn["socket"];
+
+		/* Send a close command */
+		$cmd = "close " . $res_id;
+
+		mapi_write($socket, format_command($cmd));
+		$last_error = mapi_read($socket);
+
+		if ($last_error != "") {
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+
+	function mapi_generate_id(){
+		global $connection_pool;
+
+		$connections = array();
+		if ($connection_pool !== null) {
+		    foreach($connection_pool as $conn) {
+			$connections[] = $conn["id"];
+		    }
+		}
+
+		$id = hash("sha1", time());
+		if (count($connections) > 0) {
+			while (in_array($id, $connections) ) {
+				$id = hash("sha1", time());
+			}
+		}
+
+		return $id;
+	}
+
+
+	function format_query($query, $lang) {
+		if ($lang == LANG_SQL) {
+
+			return "s" . $query . "\n;";
+		}
+		return FALSE;
+	}
+
+	/* Write data through a socket; make sure that the buffer is actually flushed */
+	function socket_flush($socket, $data) {
+		$buf = 0;
+		$bytes = strlen($data);
+
+		if ($socket == NULL) {
+			return FALSE;
+		}
+
+		while ( ($bytes - $buf) > 0 ) {
+			$buf += socket_write($socket, substr($data, $buf, $bytes), $bytes - $buf);
+			//print "Buf: " . $buf . "\n";
+
+			if ($buf == FALSE) {
+				return FALSE;
+			}
+
+		}
+		return $buf;
+	}
+
+	function mapi_quote($string, $size) {
+		$quoted_string = ""; # upper bound: $size * 2 + 1
+
+		$index = 0; // current position in the original string
+		$t = 0; // current position in the quoted string
+
+		/* Parse the original string character by character and copy it in a new buffer escaping characters */
+        while ($size < 0 ? $quoted_string[$t] : $size > 0) {
+                if ($size > 0)
+                        $size--;
+                switch ($string[$index]) {
+                	case '\n':
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = 'n';
+                        break;
+                	case '\t':
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = 't';
+                        break;
+                	case PLACEHOLDER:
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = PLACEHOLDER;
+                        break;
+                	case '\\':
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = '\\';
+                        break;
+                	case '\'':
+                        $quoted_string[$t++] = '\'';
+                       break;
+                	case '\"':
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = '"';
+                        break;
+                	case '\0':
+                        $quoted_string[$t++] = '\\';
+                        $quoted_string[$t++] = '0';
+                        break;
+                default:
+                        $quoted_string[$t++] = $string[$index];
+                        break;
+                }
+                $index++;
+                /* also deal with binaries */
+        }
+
+		if (is_array($quoted_string)) {
+			$quoted_string = implode($quoted_string);
+		}
+        return $quoted_string;
+	}
+
+?>
new file mode 100644
--- /dev/null
+++ b/lib/php_monetdb.php
@@ -0,0 +1,537 @@
+<?php
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+?>
+
+<?php
+	/**
+	* php_monetdb.php
+	* Implementation of the driver API.
+	*
+	* This library relies on the native PHP mapi implementation.
+	* 
+	* This file should be included by all pages that want to make use of a
+ 	* database connection.
+	*
+	* Synopsis of the provided functions:
+	*
+	* function monetdb_connect() *
+	* function monetdb_disconnect()  *
+    * function monetdb_connected() *
+	* function monetdb_query($query)  *
+	* function monetdb_fetch_assoc($hdl) *
+	* function monetdb_fetch_object($hdl) 
+	* function monetdb_num_rows($hdl)  *
+	* function monetdb_affected_rows($hdl) * 
+	* function monetdb_last_error()  *
+	* function monetdb_insert_id($seq)  *
+	* function monetdb_quote_ident($str) *
+	* function monetdb_escape_string($str) * 
+	**/
+
+	/**
+	* php_mapi.inc is a native (socket based) php implementation of the MAPI communication protocol.
+	*/
+	require 'php_mapi.inc';
+	
+	/**
+	* register a 'monetdb' extension to retain compatibility with wht Cimpl based scripts.
+	*/
+	
+	/**
+	 * Opens a connection to a MonetDB server.  
+	 * 
+	 * @param string language to be used (sql)
+	 * @param string hostname to connect to (default is localhost)
+	 * @param int    port to use (default is 50000)
+	 * @param string username (default is monetdb)
+	 * @param string password (default is monetdb)
+	 * @param string database to use (default is demo)
+	 * @param string hash function to use during authentication (defaults to SHA1) 
+	 * @return bool TRUE on success or FALSE on failure 
+	 */
+	
+	function monetdb_connect($lang = "sql", $host = "127.0.0.1", $port = 50000, $username = "monetdb", $password = "monetdb", $database = "demo", $hashfunc = "") {
+	 	$options["host"] = $host;
+		$options["port"] = $port;
+
+		$options["username"] = $username;
+		$options["password"] = $password;
+		
+		$options["database"] = $database; 
+		$options["persistent"] = FALSE;
+	
+	
+	    if ($hashfunc == "") {
+		    $hashfunc = "sha1";
+	    }
+	    
+	    if ($lang == "") {
+	        $lang = "sql";
+	    }
+	    
+	    $options["hashfunc"] = $hashfunc;
+        $options["lang"]     = $lang;
+				
+		return mapi_connect_proxy($options);
+	}
+
+	/**
+	 * Opens a persistent connection to a MonetDB server.  
+	 * First, when connecting, the function would first try to find a (persistent) link that's already open with the same host, 
+	 * username and password. If one is found, an identifier for it will be returned instead of opening a new connection.
+	 *
+	 * Second, the connection to the SQL server will not be closed when the execution of the script ends. 
+	 * Instead, the link will remain open for future use (monetdb_close() will not close links established by monetdb_pconnect()).
+	 *
+	 * This type of link is therefore called 'persistent'. 
+	 *
+	 * @param string language to be used (sql)
+	 * @param string hostname to connect to (default is localhost)
+	 * @param int    port to use (default is 50000)
+	 * @param string username (default is monetdb)
+	 * @param string password (default is monetdb)
+	 * @param string database to use (default is demo)
+	 * @param string hash function to use during authentication (defaults to SHA1)
+	 * @return bool TRUE on success or FALSE on failure 
+	 */
+	
+  function monetdb_pconnect($lang = "sql", $host = "127.0.0.1", $port = 500000, $username = "monetdb", $password = "monetdb", $database = "demo", $hashfunc = "") {
+
+	 	$options["host"] = $host;
+		$options["port"] = $port;
+
+		$options["username"] = $username;
+		$options["password"] = $password;
+		$options["database"] = $database; 
+		$options["persistent"] = TRUE;
+		
+		if ($hashfunc == "") {
+		    $hashfunc = "sha1";
+	    }
+	    
+	    if ($lang == "") {
+	        $lang = "sql";
+		} else if (strstr($lang, "sql") == $lang) {
+			$lang = "sql";
+		}
+	    
+	    $options["hashfunc"] = $hashfunc;
+        $options["lang"]     = $lang;
+		
+		return mapi_connect_proxy($options);
+	}
+
+	/**
+	 * Disconnects the connection to the database.
+	 *
+	 * @param resource connection instance
+	 */
+	function monetdb_disconnect($conn=NULL) {
+		$num_args = func_num_args();
+		
+		if ($num_args == 0) {
+			$conn = mapi_get_current_conn();
+			mapi_close(NULL);
+		} else {
+			mapi_close($conn);
+		}
+	}
+	
+	/**
+	 * Returns whether a connection to the database has been made, and has
+	 * not been closed yet.  Note that this function doesn't guarantee that
+	 * the connection is alive or usable.
+	 *
+	 * @param resource connection instance
+	 * @return bool TRUE if there is a connection, FALSE otherwise
+	 *
+	 */
+	function monetdb_connected($connection=NULL) {
+		$num_args = func_num_args();
+		
+		if ($num_args == 0){
+			return mapi_connected(mapi_get_current_conn());
+		}
+		
+		return mapi_connected($connection);
+	}
+	
+	/**
+	 * Executes the given query on the database.
+	 *
+	 * @param resource connection instance
+	 * @param string the SQL query to execute
+	 * @return resource a query handle or FALSE on failure
+	 */
+	function monetdb_query($connection=NULL, $query="") {
+		$num_args = func_num_args();
+		
+		if ($num_args == 1){
+			$arg = func_get_arg(0);
+			if (is_string($arg)) {
+				$conn = mapi_get_current_conn();
+				if ($conn != NULL) {
+					return mapi_execute($conn, $arg);
+				} 
+			}
+			
+		} else {
+			return mapi_execute($connection, $query);
+		}
+		
+		return FALSE;
+	}
+	
+	/**
+	 * Returns the number of rows in the query result.
+	 *
+	 * @param resouce the query resource
+	 * @return int the number of rows in the result; FALSE if the query did not return any result set
+	 */
+	function monetdb_num_rows($hdl) {
+		if ($hdl["operation"] == Q_TABLE || $hdl["operation"] == Q_BLOCK ) {
+			return $hdl["query"]["rows"];
+		} else {
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Returns the number of fields in the query result.
+	 *
+	 * @param resouce the query resource
+	 * @return int the number of fields in the result; FALSE if the query did not return any result set
+	 */	
+	function monetdb_num_fields($hdl) {
+    	if ($hdl["operation"] == Q_TABLE || $hdl["operation"] == Q_BLOCK ) {
+  			return $hdl["query"]["fields"];
+  		} else {
+  			return FALSE;
+  		}
+	}
+	
+	/**
+	 * Returns an array containing column values as value. 
+	 * For efficiency reasons the array pointer is not reset when calling monetdb_fetch_row
+	 * specifying a row value. 
+	 *
+	 * @param resource the query handle
+	 * @param int the position of the row to retrieve
+	 * @return array the next row in the query result as associative array or
+	 *         FALSE if no more rows exist
+	 */
+	function monetdb_fetch_row(&$hdl, $row=-1) {	
+		global $last_error;
+		
+		if ($hdl["operation"] != Q_TABLE && $hdl["operation"] != Q_BLOCK ) {
+			return FALSE;
+		}
+	
+		if ($row == -1){
+		  // Parse the tuple and present it to the user
+			$entry = current($hdl["record_set"]);
+			
+			//advance the array of one position
+			next($hdl["record_set"]);
+
+		} else {
+			if ($row < $hdl["query"]["rows"]) {
+			  /* Parse the tuple and present it to the user*/
+				$entry = $hdl["record_set"][$row-1];
+			}
+			else {
+				$last_error = "Index out of bound\n";
+				return FALSE;
+			}
+		}	
+		
+    if ($entry) {
+		  return php_parse_row($entry);
+	  }
+	  
+	  return $entry;
+	}
+
+	
+	/**
+	 * Returns an associative array containing the column names as keys, and
+	 * column values as value.
+	 *
+	 * @param resource the query handle
+	 * @param int the position of the row to retrieve
+	 * @return array the next row in the query result as associative array or
+	 *         FALSE if no more rows exist
+	 */	
+	function monetdb_fetch_assoc(&$hdl, $row=-1) {
+		if ($hdl["operation"] != Q_TABLE && $hdl["operation"] != Q_BLOCK ) {
+			return FALSE;
+		}
+		
+		// first retrieve the row as an array
+		$fetched_row =  monetdb_fetch_row($hdl, $row);
+		
+		if ($fetched_row == FALSE) {
+			return FALSE;
+		}
+		
+		// now hash the array by field name		
+		$hashed = array();
+
+		$i = 0;
+		foreach ($hdl["header"]["fields"] as $field) {
+			$field = str_replace(" ", "", $field);
+			$hashed[$field] = $fetched_row[$i];
+			$i++;
+		}
+		
+		return $hashed;
+	}
+
+
+	/**
+	 * Returns the result in the given query resource as object one row at a time.  Column
+	 * names become members of the object through which the column values
+	 * can be retrieved.
+	 *
+	 * @param resource the query handle
+	 * @param int the position of the row to retrieve
+	 * @return the query result as object or FALSE if there are no more rows
+	 */
+	function monetdb_fetch_object(&$hdl, $row=-1)  {
+		
+		if ($hdl["operation"] != Q_TABLE && $hdl["operation"] != Q_BLOCK ) {
+			return FALSE;
+		}
+		
+		if (($row_array =  monetdb_fetch_assoc($hdl, $row)) == FALSE) {
+			return FALSE;
+		}
+		
+		$row_object = new stdClass();
+		if (is_array($row_array) && count($row_array) > 0) {
+			foreach ($row_array as $name=>$value) {
+		   		$name = strtolower(trim($name));
+		        if (!empty($name)) {
+		        	$row_object->$name = $value;
+		        }
+		     }
+		 }
+		
+		return $row_object;
+	}
+	
+	/**
+	* Returns the name of the field at position $field
+	*
+	* @param resource the query handle
+	* @param int field number
+	* @return string the field name, FALSE is an error occured.
+	*/
+	function monetdb_field_name(&$hdl, $field) {
+	    if (is_array($hdl) && $field >= 0) {
+            if ($hdl["operation"] == Q_TABLE || $hdl["operation"] == Q_BLOCK ) {
+                if ($hdl["header"]["fields"] != "" ) {
+                    return $hdl["header"]["fields"][$field];
+                }
+		    }
+    	}	
+    	return FALSE;
+	}
+	
+	/**
+	 * Returns the number of affected rows for an UPDATE, INSERT or DELETE
+	 * query.  The number of affected rows typically is 1 for INSERT
+	 * queries.
+	 *
+	 * @param resource the query handle
+	 * @return int the number of affected rows, FALSE if the last executed query did not affect any row.
+	 */	
+	function monetdb_affected_rows($hdl) {
+		if ($hdl["operation"] != Q_UPDATE) {
+			return FALSE;
+		} 
+		
+		return $hdl["query"]["affected"];
+	}
+	
+	
+	/**
+	* Check if the query handle contains a result set. Note: the result set may be empty.
+	*
+	* @param resource the query handle
+	* @return bool TRUE if the query contains a result set, FALSE otherwise.
+	*/
+	function monetdb_has_result($hdl) {
+	    if (($hdl["operation"] == Q_TABLE) || ($hdl["operation"] == Q_BLOCK)) {
+	        return TRUE;
+	    }
+	    
+	    return FALSE;
+	}
+	
+	/**
+	 * Returns the last error reported by the database.
+	 *
+	 * @return string the last error reported
+	 */
+	function monetdb_last_error() {
+		global $last_error;
+		return $last_error;
+	}
+	
+	/**
+	 * Generates the next id in the sequence $seq
+	 *
+	 * @param resource connection instance
+	 * @param seq sequence whose next
+	 * value we want to retrieve
+	 * @return string the ID of the last tuple inserted. FALSE if an error occurs
+	 */
+	function monetdb_insert_id($connection = NULL, $seq)  {
+		$num_args = func_num_args();
+		
+		if ($num_args == 1) {
+			$connection = mapi_get_current_conn();
+			$seq = func_get_arg(0);
+		}
+		
+		if (is_string($seq)) {
+			$query = "SELECT NEXT VALUE FOR ".monetdb_quote_ident($seq)."";
+			$res = monetdb_query($connection, $query);
+			$row = monetdb_fetch_assoc($result);
+            return($row[$seq]);
+		}
+		
+		return FALSE;
+	}
+	
+	/**
+	 * Returns a 'quoted identifier' suitable for MonetDB.
+	 * This utility function can be used in queries to for instance quote
+	 * names of tables of columns that otherwise would be a mistaken for a
+	 * keyword.
+	 * NOTE: the given string is currently not checked for validity, hence
+	 *       the output of this function may be an invalid identifier.
+	 *
+	 * @param string the identifier to quote
+	 * @return string the quoted identifier
+	 *
+	 */
+	function monetdb_quote_ident($str) {
+		return('"'. $str .'"');
+	}
+
+	/**
+	 * Returns an 'escaped' string that can be used for instance within
+	 * single quotes to represent a CHARACTER VARYING object in SQL.
+	 *
+	 * @param string the string to escape
+	 * @return string the escaped string
+	 */
+	function monetdb_escape_string($str) {
+		return mapi_quote($str, strlen($str));
+	}
+	
+	/**
+	* Free the result set memory.
+	* 
+	* @param resource the query handle.
+	* @return bool returns TRUE on success or FALSE on failure.
+	*
+	*/
+	function monetdb_free_result(&$hdl) {
+		$conn_id = $hdl["conn"];
+		$res_id = $hdl["query"]["id"];
+
+		/* Release the result set on server */
+		mapi_free_result($conn_id, $res_id);
+		
+		
+		if (isset($hdl) && is_array($hdl)) {
+			foreach($hdl as $field) {
+				if (isset($field)) {
+					unset($field);
+				}
+			}
+			
+			unset($hdl);
+			
+			return TRUE;
+		}
+		
+		return FALSE;
+	}
+		
+	
+	/* 
+	 * These functions are not present in the original Cimpl implementation
+	 */
+	
+	/**
+	* Create a new savepoint ID
+	* @param resource connection instance
+	* @return bool TRUE if the ID has been correctly generated, FALSE otherwise.
+	*/
+	function create_savepoint(&$conn) {
+		if ($conn != NULL) {
+			$index = count($conn["transactions"]);
+		
+			$id = "monetdbsp" . $index;
+			array_push($conn["transactions"], $id);
+			
+			return TRUE;
+		}
+		
+		return FALSE;
+	}
+	
+	/**
+	* Release a savepoint ID.
+	* @param resource connection instance
+	* @return bool TRUE if the ID has been correctly released, FALSE otherwise.
+	*/
+	function release_savepoint(&$conn) {
+		if ($conn != NULL) { 
+			array_pop($conn["transactions"]);
+			return TRUE;
+		}
+		
+		return FALSE;
+	}
+
+	/**
+	* Return the current (last generated) savepoint ID.
+	* @param resource connection instance
+	* @return string savepoint ID. I no savepoints are available, FALSE is returned.
+	*/
+	function get_savepoint(&$conn) {
+		if (count($conn["transactions"]) == 0) {
+			return FALSE;
+		}
+		
+		// return the last element in the array
+		return $conn["transactions"][count($conn["transactions"])-1];
+	}
+
+	/**
+	* Sets auto commit mode on/off
+	* @param resource connection instance
+	* @param bool TRUE to turn auto commit on, FALSE to turn it off.
+	* @return bool TRUE is auto commit mode was correctly set. FALSE otherwise.
+	*/
+	function auto_commit($conn, $flag=TRUE) {
+		if ($conn["socket"] != NULL) {
+			$cmd = "auto_commit " . (int) $flag;
+			mapi_write($conn["socket"], format_command($cmd));
+			mapi_read($conn["socket"]);
+			
+			return TRUE;
+		}
+		return FALSE;
+	}
+	
+?>
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,5 @@
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0.  If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.