From e8d974fa1ae62932d5959c811fd3cbe8da04020d Mon Sep 17 00:00:00 2001 From: "Kevin M. Rosenberg" Date: Sat, 29 Oct 2022 06:30:27 +0200 Subject: Import cl-sql_6.7.2-1.debian.tar.xz [dgit import tarball cl-sql 6.7.2-1 cl-sql_6.7.2-1.debian.tar.xz] --- README.Debian | 9 + changelog | 1936 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cl-sql-tests.docs | 1 + cl-sql.doc-base | 12 + clsql-init.lisp | 3 + compat | 1 + control | 106 +++ copyright | 34 + docs | 4 + rules | 177 +++++ source/format | 1 + watch | 2 + 12 files changed, 2286 insertions(+) create mode 100644 README.Debian create mode 100644 changelog create mode 100644 cl-sql-tests.docs create mode 100644 cl-sql.doc-base create mode 100644 clsql-init.lisp create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 docs create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/README.Debian b/README.Debian new file mode 100644 index 0000000..fec6dda --- /dev/null +++ b/README.Debian @@ -0,0 +1,9 @@ +The Debian Package CL-SQL +-------------------------- + +This is the CLSQL Common Lisp system packaged for Debian. + +Anonymous CVS for this code is available at: +:pserver:anoncvs@cvs.med-info.com:/pubcvs clsql + +Kevin M. Rosenberg , Thu, 25 Apr 2002 19:13:41 -0600 diff --git a/changelog b/changelog new file mode 100644 index 0000000..2e9469e --- /dev/null +++ b/changelog @@ -0,0 +1,1936 @@ +cl-sql (6.7.2-1) unstable; urgency=medium + + * LICENSE: CLSQL is now licensed under MIT license + + -- Kevin M. Rosenberg Sat, 29 Oct 2022 04:30:27 +0000 + +cl-sql (6.7.1-3) unstable; urgency=medium + + * Remove cl-sql-sqlite from cl-sql-tests dependencies (closes: 973358) + + -- Kevin M. Rosenberg Thu, 29 Oct 2020 17:35:49 +0000 + +cl-sql (6.7.1-2) unstable; urgency=medium + + * Remove cl-sql-sqlite binary package (closes: 972124) + + -- Kevin M. Rosenberg Tue, 13 Oct 2020 02:51:11 +0000 + +cl-sql (6.7.1-1) unstable; urgency=medium + + * New upstream + + -- Kevin M. Rosenberg Thu, 01 Oct 2020 16:39:41 +0000 + +cl-sql (6.7.0.1-2) unstable; urgency=medium + + * Source only package upload (closes: 970609) + + -- Kevin M. Rosenberg Mon, 28 Sep 2020 20:42:03 +0000 + +cl-sql (6.7.0.1-1) unstable; urgency=medium + + * Clean source file package + * Update Debian compatibility + + -- Kevin M. Rosenberg Mon, 20 Jul 2020 19:53:44 +0000 + +cl-sql (6.7.0-1) unstable; urgency=medium + + * New upstream + + -- Kevin M. Rosenberg Tue, 26 Jan 2016 14:40:50 -0700 + +cl-sql (6.6.3-3) unstable; urgency=medium + + * debian/rules: Separate install target to install-indep and + install-arch (closes:806609) + + -- Kevin M. Rosenberg Thu, 10 Dec 2015 12:35:59 -0700 + +cl-sql (6.6.3-2) unstable; urgency=medium + + * Improve building/linking on non-AMD64 architectures, thanks to + Edmund Grimley Evans and Dejan Latinovic (closes:798017) + + -- Kevin M. Rosenberg Thu, 10 Dec 2015 12:10:32 -0700 + +cl-sql (6.6.3-1) unstable; urgency=medium + + * New upstream + * Use gcc for linking (closes:755064) + + -- Kevin M. Rosenberg Sat, 29 Aug 2015 18:13:55 -0600 + +cl-sql (6.6.2-1) unstable; urgency=medium + + * New upstream + + -- Kevin M. Rosenberg Mon, 30 Mar 2015 14:46:53 -0600 + +cl-sql (6.6.1-1) unstable; urgency=medium + + * {uffi,db-mysql}/Makefile: Change build hardening to +all,-pie + + -- Kevin M. Rosenberg Wed, 18 Mar 2015 21:30:11 -0600 + +cl-sql (6.6.0-1) unstable; urgency=medium + + * New upstream + * {uffi,db-mysql}/Makefile: Add build hardening + + -- Kevin M. Rosenberg Thu, 26 Feb 2015 15:32:46 -0700 + +cl-sql (6.5.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 04 Mar 2014 10:10:27 -0700 + +cl-sql (6.4.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 18 Apr 2013 11:21:25 -0600 + +cl-sql (6.4.0-1) unstable; urgency=low + + * New release + + -- Kevin M. Rosenberg Wed, 19 Dec 2012 10:01:33 -0700 + +cl-sql (6.3.0-1) unstable; urgency=low + + * New release + + -- Kevin M. Rosenberg Tue, 04 Sep 2012 16:58:17 -0600 + +cl-sql (6.2.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 26 Apr 2012 10:05:22 -0600 + +cl-sql (6.1.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 20 Dec 2011 19:58:29 -0700 + +cl-sql (6.1.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 19 Dec 2011 10:13:58 -0700 + +cl-sql (6.0.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 21 Sep 2011 10:57:59 -0600 + +cl-sql (6.0.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 03 Aug 2011 08:47:58 -0600 + +cl-sql (5.4.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 16 Jul 2011 09:13:15 -0600 + +cl-sql (5.3.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 20 Jun 2011 13:20:12 -0600 + +cl-sql (5.3.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 12 Jun 2011 09:49:33 -0600 + +cl-sql (5.3.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 01 Apr 2011 12:08:02 -0600 + +cl-sql (5.3.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 30 Mar 2011 23:32:06 -0600 + +cl-sql (5.3.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 29 Mar 2011 18:25:13 -0600 + +cl-sql (5.2.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 07 Nov 2010 09:47:02 -0700 + +cl-sql (5.1.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 20 Sep 2010 21:56:46 -0600 + +cl-sql (5.1.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 16 Aug 2010 16:29:09 -0600 + +cl-sql (5.1.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 16 Aug 2010 11:27:41 -0600 + +cl-sql (5.1.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 15 May 2010 15:20:38 -0600 + +cl-sql (5.1.0-1) unstable; urgency=low + + * New upstream - depends on UFFI 2.x + + -- Kevin M. Rosenberg Tue, 20 Apr 2010 12:37:13 -0600 + +cl-sql (5.0.6-1) unstable; urgency=low + + * New upstream + * Switch to dpkg-source 3.0 (quilt) format + + -- Kevin M. Rosenberg Fri, 16 Apr 2010 00:44:41 -0600 + +cl-sql (5.0.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 21 Mar 2010 01:11:20 -0600 + +cl-sql (5.0.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 17 Feb 2010 10:02:14 -0700 + +cl-sql (5.0.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 11 Feb 2010 15:18:04 -0700 + +cl-sql (5.0.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 08 Feb 2010 16:18:52 -0700 + +cl-sql (5.0.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 07 Feb 2010 22:34:13 -0700 + +cl-sql (5.0.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 07 Feb 2010 02:48:20 -0700 + +cl-sql (4.3.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 29 Jan 2010 16:48:24 -0700 + +cl-sql (4.3.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 28 Jan 2010 18:36:06 -0700 + +cl-sql (4.3.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 28 Jan 2010 15:10:11 -0700 + +cl-sql (4.3.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 07 Jan 2010 11:03:33 -0700 + +cl-sql (4.2.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 10 Dec 2009 17:03:31 -0700 + +cl-sql (4.1.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 12 Nov 2009 00:41:04 -0700 + +cl-sql (4.1.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 04 Sep 2009 13:02:14 -0600 + +cl-sql (4.1.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 31 Aug 2009 22:43:56 -0600 + +cl-sql (4.0.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 31 Aug 2009 14:36:30 -0600 + +cl-sql (4.0.5-5) unstable; urgency=low + + * debian/rules: Fix bug introduced by change to dh-lisp (closes:540816) + + -- Kevin M. Rosenberg Mon, 10 Aug 2009 10:05:16 -0600 + +cl-sql (4.0.5-4) unstable; urgency=low + + * debian/control: + - Add Vcs-Browser field + - Convert to dh-lisp + * debian/rules: + - Simplify install rules utilizing more debhelper functions + - Convert to dh-lisp + + -- Kevin M. Rosenberg Tue, 04 Aug 2009 02:21:14 -0600 + +cl-sql (4.0.5-3) unstable; urgency=low + + * control: Update debhelper compat to 7, add Homepage and Vgs-Git + fields. + * rules: update to DH 7 compatible + + -- Kevin M. Rosenberg Sun, 02 Aug 2009 00:21:45 -0600 + +cl-sql (4.0.5-2) unstable; urgency=low + + * Change section from devel to lisp + * Update debhelper compat version to 5 + + -- Kevin M. Rosenberg Sun, 26 Jul 2009 20:28:14 -0600 + +cl-sql (4.0.5-1) unstable; urgency=low + + * New upstream version + * Change build dependency to be independent of libmysqlclient version + (closes:538459) + + -- Kevin M. Rosenberg Sun, 26 Jul 2009 17:30:19 -0600 + +cl-sql (4.0.4-1) unstable; urgency=low + + * Update Oracle FFI based on changes in CLSQL. Thanks to Paul + Metcalfe, Liam Healy, and Alan Salewski (closes:481029) + + -- Kevin M. Rosenberg Sat, 07 Mar 2009 13:32:00 -0700 + +cl-sql (4.0.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 11 Dec 2007 18:14:13 -0700 + +cl-sql (4.0.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 23 Oct 2007 08:01:21 -0600 + +cl-sql (4.0.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 17 Sep 2007 08:53:42 -0600 + +cl-sql (4.0.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 14 Sep 2007 11:55:03 -0600 + +cl-sql (3.8.6.1-4) unstable; urgency=low + + * make package binNMU safe (closes:435968) + + -- Kevin M. Rosenberg Tue, 28 Aug 2007 08:40:23 -0600 + +cl-sql (3.8.6.1-3) unstable; urgency=low + + * debian/control: actually use the binary:Version for any-to-any dependencies + (closes:434812) + + -- Kevin M. Rosenberg Thu, 26 Jul 2007 16:55:29 -0600 + +cl-sql (3.8.6.1-2) unstable; urgency=low + + * debian/control: Tighten version dependencies. Convert source:Version to + binary:Version for any-to-any dependencies. Revert changes in 3.8.6.1-1 + (closes:434358) + + -- Kevin M. Rosenberg Mon, 23 Jul 2007 11:59:21 -0600 + +cl-sql (3.8.6.1-1) unstable; urgency=low + + * debian/control: Change architecture of binary packages which + depend on cl-sql-uffi from 'all' to 'any' since cl-sql-uffi is 'any'. + Without this change, packages which depend on cl-sql-uffi can become + uninstallable if the autobuilder fails to build the 'any' cl-sql-uffi + and the dependant 'all' package gets upgraded. (closes: 433907) + + -- Kevin M. Rosenberg Sun, 22 Jul 2007 11:37:13 -0600 + +cl-sql (3.8.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 20 Jul 2007 17:17:32 -0600 + +cl-sql (3.8.5-2) unstable; urgency=low + + * debian/watch: New version + + -- Kevin M. Rosenberg Wed, 18 Jul 2007 20:26:08 -0600 + +cl-sql (3.8.5-1) unstable; urgency=low + + * debian/control: Add libmysqlclient15-dev as dependency for cl-sql-mysql + since clsql-mysql modules depends on finding the .so files to load + + -- Kevin M. Rosenberg Mon, 16 Jul 2007 02:16:22 -0600 + +cl-sql (3.8.4-2) unstable; urgency=low + + * debian/control: Change from ${Source-Version} to ${source:Version} + + -- Kevin M. Rosenberg Fri, 22 Jun 2007 11:00:14 -0600 + +cl-sql (3.8.4-1) unstable; urgency=low + + * Don't load libmysqlclient.so. This is automatically loaded + when loading clsql_uffi.so. Closes: #427875 + + -- Kevin M. Rosenberg Fri, 22 Jun 2007 10:54:33 -0600 + +cl-sql (3.8.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 25 Apr 2007 12:14:05 -0600 + +cl-sql (3.8.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 23 Mar 2007 07:37:15 -0600 + +cl-sql (3.8.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 26 Jan 2007 08:30:28 -0700 + +cl-sql (3.8.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 30 Dec 2006 18:28:40 -0700 + +cl-sql (3.7.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 30 Dec 2006 14:36:03 -0700 + +cl-sql (3.7.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 30 Nov 2006 20:09:12 -0700 + +cl-sql (3.7.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 16 Oct 2006 13:35:28 -0600 + +cl-sql (3.7.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 16 Oct 2006 13:24:00 -0600 + +cl-sql (3.7.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 16 Oct 2006 13:05:53 -0600 + +cl-sql (3.7.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 3 Oct 2006 05:01:09 -0600 + +cl-sql (3.7.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 02 Oct 2006 23:05:55 -0600 + +cl-sql (3.7.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 20 Sep 2006 21:24:22 -0600 + +cl-sql (3.7.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 4 Sep 2006 21:17:05 -0600 + +cl-sql (3.7.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 30 Aug 2006 04:03:18 -0600 + +cl-sql (3.6.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 29 Aug 2006 09:04:41 -0600 + +cl-sql (3.6.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 14 Aug 2006 00:53:43 -0600 + +cl-sql (3.6.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 12 Aug 2006 15:18:47 -0600 + +cl-sql (3.6.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 12 Aug 2006 10:18:05 -0600 + +cl-sql (3.6.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 7 Jul 2006 13:00:59 -0600 + +cl-sql (3.6.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 4 Jul 2006 19:28:44 -0600 + +cl-sql (3.6.1-1) unstable; urgency=low + + * New upstream, add documentation for db-reader and + verified correct operation with symbol-function of symbol + (closes: 352567) + + -- Kevin M. Rosenberg Mon, 15 May 2006 21:07:47 -0600 + +cl-sql (3.6.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 9 May 2006 09:41:58 -0600 + +cl-sql (3.5.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 3 May 2006 07:50:05 -0600 + +cl-sql (3.5.6-3) unstable; urgency=low + + * Rebuild to update library dependencies (closes: 361849) + * Rebuild for m68k gcc change (closes: 359273) + + -- Kevin M. Rosenberg Tue, 11 Apr 2006 16:53:14 -0600 + +cl-sql (3.5.6-2) unstable; urgency=low + + * Update dependencies (closes: 358631) + + -- Kevin M. Rosenberg Thu, 23 Mar 2006 10:10:07 -0700 + +cl-sql (3.5.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 20 Mar 2006 19:45:36 -0700 + +cl-sql (3.5.5-1) unstable; urgency=low + + * Add GNU uname (closes: 355919) + + -- Kevin M. Rosenberg Wed, 8 Mar 2006 12:07:27 -0700 + +cl-sql (3.5.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 28 Feb 2006 08:48:40 -0700 + +cl-sql (3.5.3-1) unstable; urgency=low + + * New upstream + * Really commit patch for GNU/kFreeBSD (closes: 345219) + + -- Kevin M. Rosenberg Mon, 16 Jan 2006 14:43:55 -0700 + +cl-sql (3.5.2-2) unstable; urgency=low + + * Commit patch for GNU/kFreeBSD (closes: 345219) + + -- Kevin M. Rosenberg Sat, 31 Dec 2005 11:14:04 -0700 + +cl-sql (3.5.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 23 Dec 2005 03:46:59 -0700 + +cl-sql (3.5.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 2 Dec 2005 08:19:30 -0700 + +cl-sql (3.5.0-1) unstable; urgency=low + + * New upstream (closes: 339842) + * Change libmyclient run-time requirement (closes: 339824) + + -- Kevin M. Rosenberg Sat, 26 Nov 2005 08:58:22 -0700 + +cl-sql (3.4.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 25 Nov 2005 10:41:34 -0700 + +cl-sql (3.4.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 16 Nov 2005 00:57:49 -0700 + +cl-sql (3.4.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 15 Nov 2005 23:42:20 -0700 + +cl-sql (3.4.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 14 Nov 2005 19:41:24 -0700 + +cl-sql (3.4.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 13 Nov 2005 02:11:25 -0700 + +cl-sql (3.4.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 12 Nov 2005 10:44:37 -0700 + +cl-sql (3.4.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 11 Nov 2005 09:24:48 -0700 + +cl-sql (3.4.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 10 Nov 2005 15:17:40 -0700 + +cl-sql (3.3.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 4 Nov 2005 12:22:18 -0700 + +cl-sql (3.3.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 30 Oct 2005 21:20:34 -0700 + +cl-sql (3.3.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 25 Oct 2005 11:43:31 -0600 + +cl-sql (3.3.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 25 Oct 2005 10:16:34 -0600 + +cl-sql (3.3.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 17 Sep 2005 19:28:58 -0600 + +cl-sql (3.2.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 17 Sep 2005 15:53:47 -0600 + +cl-sql (3.2.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 8 Sep 2005 09:05:38 -0600 + +cl-sql (3.2.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 7 Sep 2005 09:47:10 -0600 + +cl-sql (3.2.1-2) unstable; urgency=low + + * Add /usr/lib/ to search paths. (closes:317097) + + -- Kevin M. Rosenberg Wed, 6 Jul 2005 06:16:51 -0600 + +cl-sql (3.2.1-1) unstable; urgency=low + + * New upstream + * Add /etc/clsql-init.lisp site initialization file. + + -- Kevin M. Rosenberg Tue, 5 Jul 2005 17:36:38 -0600 + +cl-sql (3.2.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 8 Jun 2005 18:34:07 -0600 + +cl-sql (3.1.16-1) unstable; urgency=low + + * New upstream + * Change build dependency from postgresql-dev to libpq-dev for new + Etch versions of postgresql + + -- Kevin M. Rosenberg Tue, 7 Jun 2005 13:36:48 -0600 + +cl-sql (3.1.15-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 18 May 2005 01:25:33 -0600 + +cl-sql (3.1.14-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 17 May 2005 13:06:45 -0600 + +cl-sql (3.1.13-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 17 May 2005 13:06:24 -0600 + +cl-sql (3.1.12-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 27 Apr 2005 15:47:40 -0600 + +cl-sql (3.1.11-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 24 Apr 2005 08:58:51 -0600 + +cl-sql (3.1.10-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 13 Apr 2005 13:00:51 -0600 + +cl-sql (3.1.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 6 Apr 2005 09:30:51 -0600 + +cl-sql (3.1.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 3 Apr 2005 18:42:47 -0600 + +cl-sql (3.1.7-1) unstable; urgency=low + + * New upstream (closes:295769) + + -- Kevin M. Rosenberg Thu, 17 Feb 2005 16:46:22 -0700 + +cl-sql (3.1.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 28 Jan 2005 17:43:48 -0700 + +cl-sql (3.1.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 5 Jan 2005 10:25:40 -0700 + +cl-sql (3.1.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 29 Dec 2004 09:49:11 -0700 + +cl-sql (3.1.3-1) unstable; urgency=low + + * New upstream + * Add cl-sql-sqlite3 binary package + + -- Kevin M. Rosenberg Tue, 21 Dec 2004 00:31:28 -0700 + +cl-sql (3.1.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 6 Dec 2004 23:09:57 -0700 + +cl-sql (3.1.1-1) unstable; urgency=low + + * New upstream + * Rebuild against mysqlclient (closes:280253) + + -- Kevin M. Rosenberg Fri, 12 Nov 2004 08:09:38 -0700 + +cl-sql (3.1.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 9 Nov 2004 11:04:04 -0700 + +cl-sql (3.0.11-1) unstable; urgency=low + + * New usptream + + -- Kevin M. Rosenberg Sat, 23 Oct 2004 09:34:31 -0600 + +cl-sql (3.0.10-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 23 Oct 2004 09:16:03 -0600 + +cl-sql (3.0.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 20 Oct 2004 20:45:40 -0600 + +cl-sql (3.0.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 3 Oct 2004 09:45:16 -0600 + +cl-sql (3.0.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 1 Oct 2004 08:38:52 -0600 + +cl-sql (3.0.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 27 Sep 2004 23:30:07 -0600 + +cl-sql (3.0.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 21 Sep 2004 17:14:24 -0600 + +cl-sql (3.0.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 9 Sep 2004 22:21:42 -0600 + +cl-sql (3.0.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 7 Sep 2004 14:45:27 -0600 + +cl-sql (3.0.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 2 Sep 2004 16:04:19 -0600 + +cl-sql (3.0.1-1) unstable; urgency=medium + + * New upstream, important bug fix for classes with multiple join + classes + + -- Kevin M. Rosenberg Wed, 1 Sep 2004 12:16:40 -0600 + +cl-sql (3.0.0-2) unstable; urgency=low + + * Change assignment in rules file + + -- Kevin M. Rosenberg Fri, 6 Aug 2004 10:09:04 -0600 + +cl-sql (3.0.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 2 Aug 2004 19:32:39 -0600 + +cl-sql (2.11.16-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 15 Jul 2004 09:32:45 -0600 + +cl-sql (2.11.15-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 14 Jul 2004 21:42:41 -0600 + +cl-sql (2.11.14-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 10 Jul 2004 19:25:15 -0600 + +cl-sql (2.11.13-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 10 Jul 2004 01:11:44 -0600 + +cl-sql (2.11.12-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 2 Jul 2004 20:41:21 -0600 + +cl-sql (2.11.11-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 23 Jun 2004 15:49:23 -0600 + +cl-sql (2.11.10-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 15 Jun 2004 18:45:47 -0600 + +cl-sql (2.11.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 13 Jun 2004 15:51:35 -0700 + +cl-sql (2.11.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 10 Jun 2004 03:43:11 -0600 + +cl-sql (2.11.7-2) unstable; urgency=low + + * Move cl-sql-oracle to contrib + + -- Kevin M. Rosenberg Wed, 9 Jun 2004 18:49:01 -0600 + +cl-sql (2.11.7-1) unstable; urgency=low + + * New upstream + * Move to contrib section (closes:253360) + + -- Kevin M. Rosenberg Wed, 9 Jun 2004 17:57:10 -0600 + +cl-sql (2.11.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 07 Jun 2004 13:01:23 -0600 + +cl-sql (2.11.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 4 Jun 2004 14:26:14 -0600 + +cl-sql (2.11.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 30 May 2004 03:33:48 -0600 + +cl-sql (2.11.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 29 May 2004 08:37:25 -0600 + +cl-sql (2.11.2-1) unstable; urgency=low + + * New upstream + * Add cl-sql-oracle binary package + + -- Kevin M. Rosenberg Thu, 27 May 2004 04:47:27 -0600 + +cl-sql (2.11.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 25 May 2004 02:38:21 -0600 + +cl-sql (2.11.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 25 May 2004 01:22:16 -0600 + +cl-sql (2.10.22-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 23 May 2004 11:46:00 -0600 + +cl-sql (2.10.21-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 23 May 2004 04:50:44 -0600 + +cl-sql (2.10.20-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 22 May 2004 21:29:10 -0600 + +cl-sql (2.10.18-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 20 May 2004 03:48:26 -0600 + +cl-sql (2.10.17-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 13 May 2004 04:48:28 -0600 + +cl-sql (2.10.16-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 12 May 2004 22:45:37 -0600 + +cl-sql (2.10.15-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 9 May 2004 18:59:19 -0600 + +cl-sql (2.10.14-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 8 May 2004 20:30:35 -0600 + +cl-sql (2.10.13-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 8 May 2004 11:41:13 -0600 + +cl-sql (2.10.12-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 6 May 2004 13:42:19 -0600 + +cl-sql (2.10.11-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 5 May 2004 02:06:19 -0600 + +cl-sql (2.10.10-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 4 May 2004 20:27:10 -0600 + +cl-sql (2.10.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 4 May 2004 12:52:19 -0600 + +cl-sql (2.10.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 3 May 2004 12:43:58 -0600 + +cl-sql (2.10.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 3 May 2004 09:50:27 -0600 + +cl-sql (2.10.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 2 May 2004 19:58:36 -0600 + +cl-sql (2.10.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 1 May 2004 14:06:43 -0600 + +cl-sql (2.10.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 1 May 2004 13:16:04 -0600 + +cl-sql (2.10.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 1 May 2004 12:18:35 -0600 + +cl-sql (2.10.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 1 May 2004 04:13:12 -0600 + +cl-sql (2.9.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 30 Apr 2004 17:07:03 -0600 + +cl-sql (2.9.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 27 Apr 2004 15:40:18 -0600 + +cl-sql (2.9.4-2) unstable; urgency=low + + * Change depends to libmysqlclient-dev (closes:246039) + + -- Kevin M. Rosenberg Mon, 26 Apr 2004 17:35:16 -0600 + +cl-sql (2.9.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 24 Apr 2004 07:54:45 -0600 + +cl-sql (2.9.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 21 Apr 2004 00:46:34 -0600 + +cl-sql (2.9.1-1) unstable; urgency=low + + * Fix shared library loading in .asd files (closes:245004) + + -- Kevin M. Rosenberg Tue, 20 Apr 2004 19:30:16 -0600 + +cl-sql (2.9.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 20 Apr 2004 17:37:40 -0600 + +cl-sql (2.8.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 19 Apr 2004 13:02:01 -0600 + +cl-sql (2.8.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 19 Apr 2004 03:01:34 -0600 + +cl-sql (2.8.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 19 Apr 2004 00:21:37 -0600 + +cl-sql (2.7.9-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 18 Apr 2004 19:16:14 -0600 + +cl-sql (2.7.8-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 18 Apr 2004 13:42:20 -0600 + +cl-sql (2.7.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 18 Apr 2004 10:17:09 -0600 + +cl-sql (2.7.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 17 Apr 2004 20:48:15 -0600 + +cl-sql (2.7.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 17 Apr 2004 11:47:40 -0600 + +cl-sql (2.7.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 17 Apr 2004 09:08:18 -0600 + +cl-sql (2.7.3-1) unstable; urgency=low + + * New upstream + * Change depends (closes:243977) + * Add new backend + + -- Kevin M. Rosenberg Fri, 16 Apr 2004 11:47:51 -0600 + +cl-sql (2.6.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 12 Apr 2004 12:13:59 -0600 + +cl-sql (2.6.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 12 Apr 2004 10:17:58 -0600 + +cl-sql (2.6.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 11 Apr 2004 08:07:25 -0600 + +cl-sql (2.6.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 11 Apr 2004 06:48:28 -0600 + +cl-sql (2.6.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sun, 11 Apr 2004 04:20:54 -0600 + +cl-sql (2.6.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 10 Apr 2004 19:49:58 -0600 + +cl-sql (2.6.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Sat, 10 Apr 2004 19:38:06 -0600 + +cl-sql (2.5.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 9 Apr 2004 12:54:21 -0600 + +cl-sql (2.5.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 9 Apr 2004 11:56:38 -0600 + +cl-sql (2.5.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 9 Apr 2004 08:07:00 -0600 + +cl-sql (2.4.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 9 Apr 2004 03:17:20 -0600 + +cl-sql (2.4.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 9 Apr 2004 02:56:46 -0600 + +cl-sql (2.4.0-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 8 Apr 2004 18:10:27 -0600 + +cl-sql (2.3.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 8 Apr 2004 15:17:20 -0600 + +cl-sql (2.3.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 8 Apr 2004 11:33:59 -0600 + +cl-sql (2.3.2-2) unstable; urgency=low + + * New upstreamc + + -- Kevin M. Rosenberg Wed, 7 Apr 2004 14:20:12 -0600 + +cl-sql (2.3.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 7 Apr 2004 13:35:01 -0600 + +cl-sql (2.3.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 7 Apr 2004 12:34:23 -0600 + +cl-sql (2.3.0-1) unstable; urgency=low + + * Integrate the clsql-usql package. + + -- Kevin M. Rosenberg Tue, 6 Apr 2004 09:35:28 -0600 + +cl-sql (2.1.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 6 Apr 2004 09:34:32 -0600 + +cl-sql (2.0.0-2) unstable; urgency=low + + * Change from libmysqlclient10 to libmysqlclient + + -- Kevin M. Rosenberg Fri, 2 Apr 2004 14:10:09 -0700 + +cl-sql (2.0.0-1) unstable; urgency=low + + * New upstream, integrating patch from Marcus Pearce + + -- Kevin M. Rosenberg Fri, 2 Apr 2004 13:34:35 -0700 + +cl-sql (1.9.2-1) unstable; urgency=low + + * Automatically detect mysql version + + -- Kevin M. Rosenberg Wed, 31 Mar 2004 16:56:23 -0700 + +cl-sql (1.9.1-1) unstable; urgency=low + + * Add better support for mysql v4 + + -- Kevin M. Rosenberg Wed, 31 Mar 2004 16:48:30 -0700 + +cl-sql (1.9.0-2) unstable; urgency=low + + * Add cl-sql-sqlite binary package + + -- Kevin M. Rosenberg Wed, 10 Mar 2004 16:07:03 -0700 + +cl-sql (1.9.0-1) unstable; urgency=low + + * Add SQLlite backend as contributed by Aurelio Bignoli + + -- Kevin M. Rosenberg Wed, 10 Mar 2004 15:19:46 -0700 + +cl-sql (1.8.7-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Tue, 25 Nov 2003 06:30:05 -0700 + +cl-sql (1.8.5-1) unstable; urgency=low + + * Fix loading on db-mysql/mysql.so on non-linux platforms + + -- Kevin M. Rosenberg Mon, 24 Nov 2003 23:17:25 -0700 + +cl-sql (1.8.4-1) unstable; urgency=low + + * Fix typo in error string + * Documentation updates + + -- Kevin M. Rosenberg Sat, 15 Nov 2003 10:23:16 -0700 + +cl-sql (1.8.3-1) unstable; urgency=low + + * More documentation, darwin changes + + -- Kevin M. Rosenberg Thu, 13 Nov 2003 09:24:30 -0700 + +cl-sql (1.8.2-1) unstable; urgency=low + + * Documentation fixes, MacOSX changes + + -- Kevin M. Rosenberg Wed, 12 Nov 2003 22:52:45 -0700 + +cl-sql (1.8.1-1) unstable; urgency=low + + * Improved shared library building + + -- Kevin M. Rosenberg Tue, 11 Nov 2003 15:26:55 -0700 + +cl-sql (1.8.0-1) unstable; urgency=low + + * Make asdf-installable + + -- Kevin M. Rosenberg Tue, 11 Nov 2003 08:06:28 -0700 + +cl-sql (1.7.9-1) unstable; urgency=low + + * Add missing documentation file + + -- Kevin M. Rosenberg Tue, 11 Nov 2003 05:30:23 -0700 + +cl-sql (1.7.8-1) unstable; urgency=low + + * Convert documentation to XML format + + -- Kevin M. Rosenberg Tue, 11 Nov 2003 01:20:20 -0700 + +cl-sql (1.7.7-1) unstable; urgency=low + + * Fix dependency for allegro-cl-trial for clsql-tests + + -- Kevin M. Rosenberg Mon, 22 Sep 2003 12:18:18 -0600 + +cl-sql (1.7.6-1) unstable; urgency=low + + * Fix connection string (closes: 208610) + + -- Kevin M. Rosenberg Thu, 4 Sep 2003 15:03:53 -0600 + +cl-sql (1.7.5-1) unstable; urgency=low + + * Another fix for previous bug (closes: 207813) + + -- Kevin M. Rosenberg Wed, 3 Sep 2003 12:21:19 -0600 + +cl-sql (1.7.4-1) unstable; urgency=low + + * New upstream (closes: 207813) + + -- Kevin M. Rosenberg Sun, 31 Aug 2003 02:19:45 -0600 + +cl-sql (1.7.3-1) unstable; urgency=low + + * New upstream + * Avoid depending on acl-pro-installer for cl-sql-adobc since that + is preventing entire source package from moving into testing since + acl-pro-installer is only supported on x86 and ppc. + + -- Kevin M. Rosenberg Sun, 31 Aug 2003 02:19:31 -0600 + +cl-sql (1.7.2-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Wed, 23 Jul 2003 12:59:46 -0600 + +cl-sql (1.7.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 21 Jul 2003 05:18:15 -0600 + +cl-sql (1.7.0-1) unstable; urgency=low + + * Update acl-compat-tester with ptester + + -- Kevin M. Rosenberg Sun, 20 Jul 2003 12:12:36 -0600 + +cl-sql (1.6.6-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 23 Jun 2003 13:25:01 -0600 + +cl-sql (1.6.5-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 12 Jun 2003 06:18:05 -0600 + +cl-sql (1.6.4-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 22 May 2003 09:42:33 -0600 + +cl-sql (1.6.3-1) unstable; urgency=low + + * New upstream + * Fix dependencies for clsql-postgresql and clsql-postgresql-socket + + -- Kevin M. Rosenberg Wed, 21 May 2003 12:12:19 -0600 + +cl-sql (1.6.1-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Fri, 16 May 2003 01:59:00 -0600 + +cl-sql (1.6.0-1) unstable; urgency=low + + * Optimize native string conversion at the expense + of ignoring locales on Allegro and Lispworks. + + -- Kevin M. Rosenberg Thu, 15 May 2003 01:32:52 -0600 + +cl-sql (1.5.4-1) unstable; urgency=low + + * Improve .asd files + + -- Kevin M. Rosenberg Tue, 6 May 2003 18:30:19 -0600 + +cl-sql (1.5.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Mon, 5 May 2003 20:22:40 -0600 + +cl-sql (1.5.2-1) unstable; urgency=low + + * Add build-depend-indep to control + + -- Kevin M. Rosenberg Mon, 5 May 2003 09:13:12 -0600 + +cl-sql (1.5.1-1) unstable; urgency=low + + * Fix for aodbc driver + + -- Kevin M. Rosenberg Thu, 1 May 2003 22:42:34 -0600 + +cl-sql (1.5.0-1) unstable; urgency=low + + * Update SBCL support in cmucl-compat package. + * Use debian/compat rather than DH_COMPAT + * Add cl-sql-tests binary with test suite + * Use libmysqlclient10 package for LGPL license + * Add cl-sql-uffi depends for cl-sql-posgresql-socket package (closes:191599) + + -- Kevin M. Rosenberg Thu, 1 May 2003 16:23:37 -0600 + +cl-sql (1.4.6-1) unstable; urgency=low + + * Documentation fix + + -- Kevin M. Rosenberg Tue, 29 Apr 2003 04:47:34 -0600 + +cl-sql (1.4.5-1) unstable; urgency=low + + * Documentation updates for cl-md5 module from Gisle Slensminde + + -- Kevin M. Rosenberg Tue, 29 Apr 2003 04:03:16 -0600 + +cl-sql (1.4.4-1) unstable; urgency=low + + * Move unwind-protect mysql backend + + -- Kevin M. Rosenberg Wed, 16 Apr 2003 15:49:46 -0600 + +cl-sql (1.4.3-1) unstable; urgency=low + + * Rebuild against new libmysql package + + -- Kevin M. Rosenberg Sat, 12 Apr 2003 05:05:03 -0600 + +cl-sql (1.4.2-1) unstable; urgency=low + + * Add optimization for pointer derefering + + -- Kevin M. Rosenberg Sat, 29 Mar 2003 15:28:57 -0700 + +cl-sql (1.4.1-1) unstable; urgency=low + + * Fix typo in clsql-mysql + + -- Kevin M. Rosenberg Wed, 26 Mar 2003 18:13:22 -0700 + +cl-sql (1.4.0-2) unstable; urgency=low + + * Move cl-md5 dependencies to cl-sql-postgresql-socket (closes: 185738) + + -- Kevin M. Rosenberg Fri, 21 Mar 2003 09:00:24 -0700 + +cl-sql (1.4.0-1) unstable; urgency=low + + * Add MD5 support to postgresql socket backend (contributed by + Robert Macomber) + + -- Kevin M. Rosenberg Sun, 2 Mar 2003 12:59:10 -0700 + +cl-sql (1.3.0-1) unstable; urgency=low + + * uffi/clsql-uffi.lisp: return NIL for numeric fields that are NULL + + -- Kevin M. Rosenberg Fri, 13 Dec 2002 05:30:08 -0700 + +cl-sql (1.2.4-1) unstable; urgency=low + + * Remove changes information from copyright file + * Minor upstream changes + + -- Kevin M. Rosenberg Wed, 4 Dec 2002 14:39:00 -0700 + +cl-sql (1.2.3-1) unstable; urgency=low + + * New upstream + + -- Kevin M. Rosenberg Thu, 14 Nov 2002 11:52:32 -0700 + +cl-sql (1.2.2-1) unstable; urgency=low + + * Remove 'load-compiled-op from .asd file + + -- Kevin M. Rosenberg Fri, 8 Nov 2002 09:49:09 -0700 + +cl-sql (1.2.1-1) unstable; urgency=low + + * New upstream version + + -- Kevin M. Rosenberg Wed, 30 Oct 2002 12:08:03 -0700 + +cl-sql (1.2.0-3) unstable; urgency=low + + * Move cl-sql-base package architecture from any to all. + + -- Kevin M. Rosenberg Mon, 28 Oct 2002 16:07:25 -0700 + +cl-sql (1.2.0-2) unstable; urgency=low + + * Split rules file into build-arch and build-indep (closes: 166800) + Thanks Bdale + + -- Kevin M. Rosenberg Mon, 28 Oct 2002 12:42:17 -0700 + +cl-sql (1.2.0-1) unstable; urgency=low + + * sql/pool.lisp: Add full support for multiprocessing locks for ACL, LW, + SCL. + + -- Kevin M. Rosenberg Mon, 21 Oct 2002 07:44:02 -0600 + +cl-sql (1.1.3-1) unstable; urgency=low + + * Add more support for SCL and performance improvements to postgresql-socket + (Thanks Doug Crosher) + * Add locks to connection pool for SCL (Doug Crosher) + + -- Kevin M. Rosenberg Mon, 21 Oct 2002 01:26:40 -0600 + +cl-sql (1.1.2-1) unstable; urgency=low + + * Fix clsql-mysql.so search path for Debian. + + -- Kevin M. Rosenberg Thu, 17 Oct 2002 19:16:52 -0600 + +cl-sql (1.1.1-1) unstable; urgency=low + + * Add more tests can existence of helper libraries. Will signal error rather than warn + if libraries not found. + + -- Kevin M. Rosenberg Thu, 17 Oct 2002 16:12:01 -0600 + +cl-sql (1.1.0-1) unstable; urgency=low + + * Add SCL support. + * Fix file type in test-suite/tester-clsql.lisp + * Add *load-truename* searching for clsql's compiled libraries + + -- Kevin M. Rosenberg Tue, 15 Oct 2002 13:34:41 -0600 + +cl-sql (1.0.1-1) unstable; urgency=low + + * Add defgeneric for generic functions + + -- Kevin M. Rosenberg Mon, 14 Oct 2002 09:18:39 -0600 + +cl-sql (1.0.0-1) unstable; urgency=low + + * Add SBCL support + + -- Kevin M. Rosenberg Sun, 13 Oct 2002 20:52:41 -0600 + +cl-sql (0.9.9-1) unstable; urgency=high + + * Add version depends for common-lisp-controller + + -- Kevin M. Rosenberg Wed, 2 Oct 2002 07:50:27 -0600 + +cl-sql (0.9.8-1) unstable; urgency=low + + * Rename .cl files to .lisp files + + -- Kevin M. Rosenberg Mon, 30 Sep 2002 04:08:25 -0600 + +cl-sql (0.9.7-1) unstable; urgency=low + + * base/conditions.cl: Fix format string error + + -- Kevin M. Rosenberg Mon, 30 Sep 2002 00:13:36 -0600 + +cl-sql (0.9.6-1) unstable; urgency=low + + * Remove cl-library logical hosts from load-library calls + + -- Kevin M. Rosenberg Sun, 29 Sep 2002 23:32:22 -0600 + +cl-sql (0.9.5-1) unstable; urgency=low + + * OpenMCL is now supported + * Fix defgeneric form in db-interfaces.cl + * Fix load-libraries call in postgresql-socket-api.cl + + -- Kevin M. Rosenberg Fri, 27 Sep 2002 08:55:33 -0600 + +cl-sql (0.9.4-2) unstable; urgency=low + + * Move documentation to cl-sql package (closes: 162491) + + -- Kevin M. Rosenberg Thu, 26 Sep 2002 15:20:19 -0600 + +cl-sql (0.9.4-1) unstable; urgency=low + + * Add reader conditionals to .asd file + + -- Kevin M. Rosenberg Wed, 25 Sep 2002 06:37:30 -0600 + +cl-sql (0.9.3-2) unstable; urgency=low + + * Add call to clc-only-compatible + + -- Kevin M. Rosenberg Sun, 22 Sep 2002 21:01:20 -0600 + +cl-sql (0.9.3-1) unstable; urgency=low + + * push onto cl:*features* if loaded with 'asdf:load-compiled-op + + -- Kevin M. Rosenberg Fri, 20 Sep 2002 00:07:25 -0600 + +cl-sql (0.9.2-1) unstable; urgency=low + + * Add information fields to .asd files + + -- Kevin M. Rosenberg Thu, 19 Sep 2002 19:40:43 -0600 + +cl-sql (0.9.1-1) unstable; urgency=low + + * New upstream fixing typo in clsql.asdf + * Fix /usr/share/common-lisp/source/clsql directory structure + + -- Kevin M. Rosenberg Thu, 19 Sep 2002 15:22:51 -0600 + +cl-sql (0.9.0-1) unstable; urgency=low + + * Move entirely to .asd files. Remove pathnames from .asd files. + + -- Kevin M. Rosenberg Wed, 18 Sep 2002 01:18:12 -0600 + +cl-sql (0.8.6-1) unstable; urgency=low + + * New upstream version, fixes problem with .asd files + + -- Kevin M. Rosenberg Fri, 6 Sep 2002 04:26:06 -0600 + +cl-sql (0.8.5-1) unstable; urgency=low + + * New upstream version + + -- Kevin M. Rosenberg Sun, 1 Sep 2002 03:00:37 -0600 + +cl-sql (0.8.4-1) unstable; urgency=low + + * New upstream version for non-CLC systems + + -- Kevin M. Rosenberg Fri, 23 Aug 2002 22:40:51 -0600 + +cl-sql (0.8.3-1) unstable; urgency=high + + * Add .asd definition files to upstream for ASDF users + * Ready for release to sarge + + -- Kevin M. Rosenberg Sat, 17 Aug 2002 20:54:35 -0600 + +cl-sql (0.8.2-1) unstable; urgency=low + + * New upstream version + * Update depends for cl-sql-aodbc to be acl-pro-instaler, not acl-installer + * Upload e-mail adress + + -- Kevin M. Rosenberg Sun, 11 Aug 2002 22:54:04 -0600 + +cl-sql (0.8.1-1) unstable; urgency=low + + * New upstream version. (Changed RedHat to Debian in upstream documentation!) + + -- Kevin M. Rosenberg Fri, 2 Aug 2002 09:45:08 -0600 + +cl-sql (0.8.0-2) unstable; urgency=low + + * Fix preinst template type to properly remove old symlinks + + -- Kevin M. Rosenberg Fri, 2 Aug 2002 09:12:32 -0600 + +cl-sql (0.8.0-1) unstable; urgency=low + + * New upstream, restructure directories for improved Common Lisp + Controller v3 compatibility. + * New binary package: cl-sql-uffi (handles common UFFI tasks for + some of the database backends). + + -- Kevin M. Rosenberg Fri, 2 Aug 2002 05:52:38 -0600 + +cl-sql (0.7.6-2) unstable; urgency=low + + * Rework logical pathnames for improved Common Lisp Controller compatibility. + + -- Kevin M. Rosenberg Wed, 31 Jul 2002 19:31:40 -0600 + +cl-sql (0.7.6-1) unstable; urgency=low + + * New upstream version, improves Common Lisp Controller compatibility. + + -- Kevin M. Rosenberg Fri, 26 Jul 2002 01:15:07 -0600 + +cl-sql (0.7.5-1) unstable; urgency=low + + * Fix Build-Depends in control file (closes: 154217) + * Rework debhelper rules file to use dh_install rather than install + * Minor change in upstream. + + -- Kevin M. Rosenberg Thu, 25 Jul 2002 09:15:44 -0600 + +cl-sql (0.7.4-1) unstable; urgency=low + + * Rework upstream source to put html documentation in html.tar.gz + * Move .so files into /usr/lib/cl-sql directory + * Move documentation into the clsql-base package, change doc-base section + to programming. + + -- Kevin M. Rosenberg Mon, 8 Jul 2002 11:10:11 -0600 + +cl-sql (0.7.3-1) unstable; urgency=low + + * Added missing documentation to upstream package. + + -- Kevin M. Rosenberg Mon, 8 Jul 2002 07:10:42 -0600 + +cl-sql (0.7.2-1) unstable; urgency=low + + * New upstream version. + + * Add the text of the LLGPL to the copyright file. + + -- Kevin M. Rosenberg Fri, 5 Jul 2002 11:06:21 -0600 + +cl-sql (0.7.1-2) unstable; urgency=low + + * Additions to the copyright file + + -- Kevin M. Rosenberg Fri, 28 Jun 2002 11:17:36 -0600 + +cl-sql (0.7.1-1) unstable; urgency=low + + * Initial Release (closes: 146792). + + -- Kevin M. Rosenberg Thu, 25 Apr 2002 19:13:41 -0600 + diff --git a/cl-sql-tests.docs b/cl-sql-tests.docs new file mode 100644 index 0000000..8a81e98 --- /dev/null +++ b/cl-sql-tests.docs @@ -0,0 +1 @@ +tests/README diff --git a/cl-sql.doc-base b/cl-sql.doc-base new file mode 100644 index 0000000..698835b --- /dev/null +++ b/cl-sql.doc-base @@ -0,0 +1,12 @@ +Document: cl-sql +Title: CLSQL Manual +Author: Kevin M. Rosenberg +Abstract: Describes the use of the CLSQL Common Lisp library. +Section: Programming + +Format: PDF +Files: /usr/share/doc/cl-sql/clsql.pdf.gz + +Format: HTML +Index: /usr/share/doc/cl-sql/html/index.html +Files: /usr/share/doc/cl-sql/html/*.html diff --git a/clsql-init.lisp b/clsql-init.lisp new file mode 100644 index 0000000..5dbb264 --- /dev/null +++ b/clsql-init.lisp @@ -0,0 +1,3 @@ +(clsql:push-library-path #p"/usr/lib/") +(clsql:push-library-path #p"/usr/lib/clsql/") + diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..d859168 --- /dev/null +++ b/control @@ -0,0 +1,106 @@ +Source: cl-sql +Section: lisp +Priority: optional +Maintainer: Kevin M. Rosenberg +Build-Depends: dh-lisp, debhelper (>= 11.0.0), default-libmysqlclient-dev, libpq-dev +Standards-Version: 4.6.1.1 +Homepage: http://clsql.kpe.io/ +Vcs-Git: git://git.kpe.io/clsql.git +Vcs-Browser: http://git.kpe.io/?p=clsql.git + +Package: cl-sql +Architecture: all +Depends: ${misc:Depends} +Recommends: cl-sql-backend +Description: SQL Interface for Common Lisp + CLSQL is a Common Lisp interface for multiple SQL databases + on multiple Common Lisp implementations. It uses the UFFI + foreign language interface. + +Package: cl-sql-uffi +Architecture: any +Depends: cl-uffi (>= 1.5.11), cl-sql (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Recommends: cl-sql-backend +Description: Common UFFI functions for CLSQL database backends + This package provides an interface to several UFFI functions used by multiple + CLSQL database backends. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-mysql +Architecture: any +Depends: cl-sql (= ${source:Version}), cl-sql-uffi (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, default-libmysqlclient-dev +Provides: cl-sql-backend +Description: CLSQL database backend, MySQL + This package enables you to use the CLSQL data access package + with MySQL databases. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-aodbc +Architecture: all +Depends: cl-sql (= ${source:Version}), cl-sql-mysql, cl-sql-postgresql, ${misc:Depends} +Provides: cl-sql-backend +Suggests: acl-pro-installer +Description: CLSQL database backend, AODBC + This package enables you to use the CLSQL data access package + with AllegroCL's AODBC databases. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-odbc +Architecture: all +Depends: cl-sql (= ${source:Version}), unixodbc-dev, cl-sql-mysql, cl-sql-postgresql, ${misc:Depends} +Provides: cl-sql-backend +Suggests: acl-pro-installer +Description: CLSQL database backend, ODBC + This package enables you to use the CLSQL data access package + with ODBC databases. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-postgresql +Architecture: all +Depends: cl-sql (= ${source:Version}), libpq-dev, cl-sql-uffi (>= ${source:Version}), ${misc:Depends} +Provides: cl-sql-backend +Description: CLSQL database backend, PostgreSQL via library + This package enables you to use the CLSQL data access package + with PostgreSQL databases using the PostgreSQL client library. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-postgresql-socket +Architecture: all +Depends: cl-sql (= ${source:Version}), cl-md5, cl-sql-uffi (>= ${source:Version}), libc6-dev, ${misc:Depends} +Provides: cl-sql-backend +Description: CLSQL database backend, PostgreSQL via sockets + This package enables you to use the CLSQL data access package + with PostgreSQL databases via a socket interface to the PostgreSQL + server. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-sqlite3 +Architecture: all +Depends: cl-sql (= ${source:Version}), libsqlite3-dev, cl-sql-uffi (>= ${source:Version}), ${misc:Depends} +Provides: cl-sql-backend +Description: CLSQL database backend, SQLite3 + This package enables you to use the CLSQL data access package + with SQLite3 databases. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-oracle +Section: contrib/lisp +Architecture: all +Depends: cl-sql (= ${source:Version}), cl-sql-uffi (>= ${source:Version}), ${misc:Depends} +Provides: cl-sql-backend +Description: CLSQL database backend, Oracle + This package enables you to use the CLSQL data access package + with Oracle databases. + CLSQL is a Common Lisp interface to SQL databases. + +Package: cl-sql-tests +Architecture: all +Depends: cl-sql, cl-sql-postgresql, cl-sql-postgresql-socket, cl-sql-mysql, cl-sql-sqlite3, cl-sql-odbc, cl-rt, ${misc:Depends} +Suggests: acl-installer, libmyodbc, unixodbc,cl-sql-aodbc +Description: Testing suite for CLSQL + This package contains a test suite for CLSQL. It requires manual + configuration of MySQL and PostgreSQL databases to execute. + A configured, licensed version of AllegroCL with ODBC setup is + required to test the clsql-aodbc interface. See the + /usr/share/doc/cl-sql-tests/README file if you want to try + running these regression tests. diff --git a/copyright b/copyright new file mode 100644 index 0000000..f571eac --- /dev/null +++ b/copyright @@ -0,0 +1,34 @@ +Debian Copyright Section +======================== + +Upstream Source URL: http://files.kpe.io/clsql +Upstream Author: Kevin M. Rosenberg +Debian Maintainer: (Same as upstream) + + +Upstream Copyright Statement +============================ + +Copyright (C) 2002-2022 by Kevin M. Rosenberg +Copyright (C) 1999-2001 Pierre R. Mai (MaiSQL ) +Copyright (C) 1999-2020 onShore Development (UncommonSQL) +Copyright (C) 1999-2001 Paul Meurer (SQL/ODBC) +Copyright (C) 2010-2015 Marc Battyani + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs b/docs new file mode 100644 index 0000000..33aa9a8 --- /dev/null +++ b/docs @@ -0,0 +1,4 @@ +NEWS +README +TODO +CONTRIBUTORS diff --git a/rules b/rules new file mode 100755 index 0000000..8db784f --- /dev/null +++ b/rules @@ -0,0 +1,177 @@ +#!/usr/bin/make -f + +plain-pkg := clsql + +pkg := cl-sql +pkg-uffi := cl-sql-uffi +pkg-mysql := cl-sql-mysql +pkg-pg := cl-sql-postgresql +pkg-pg-socket := cl-sql-postgresql-socket +pkg-aodbc := cl-sql-aodbc +pkg-odbc := cl-sql-odbc +pkg-sqlite3 := cl-sql-sqlite3 +pkg-oracle := cl-sql-oracle +pkg-tests := cl-sql-tests +all-pkgs := $(pkg) $(pkg-uffi) $(pkg-mysql) $(pkg-pg) $(pkg-pg-socket) $(pkg-aodbc) $(pkg-odbc) $(pkg-sqlite3) $(pkg-tests) + + +UPSTREAM_VER := $(shell sed -n -e "s/${pkg} (\(.*\)-[0-9A-Za-z\.]).*/\1/p" < debian/changelog |head -1) + +## Lisp sources +srcs-sql := $(wildcard sql/*.lisp) +srcs-uffi := $(wildcard uffi/*.lisp) $(wildcard uffi/*.c) +srcs-uffi-so = $(wildcard uffi/*.so) +srcs-mysql := $(wildcard db-mysql/*.lisp) $(wildcard db-mysql/*.c) +srcs-mysql-so = $(wildcard db-mysql/*.so) +srcs-pg := $(wildcard db-postgresql/*.lisp) +srcs-pg-socket := $(wildcard db-postgresql-socket/*.lisp) +srcs-aodbc := $(wildcard db-aodbc/*.lisp) +srcs-odbc := $(wildcard db-odbc/*.lisp) +srcs-sqlite3 := $(wildcard db-sqlite3/*.lisp) +srcs-oracle := $(wildcard db-oracle/*.lisp) +srcs-tests := $(wildcard tests/*.lisp) + +clc-base := usr/share/common-lisp +clc-source := $(clc-base)/source +clc-systems := $(clc-base)/systems +doc-dir := usr/share/doc/cl-sql + +clc-clsql := $(clc-source)/$(plain-pkg) + +clc-sql := $(clc-source)/clsql +lispdir-sql := $(clc-sql)/sql +clc-uffi := $(clc-source)/clsql-uffi +lispdir-uffi := $(clc-uffi)/uffi +sodir-uffi := usr/lib/clsql +clc-mysql := $(clc-source)/clsql-mysql +lispdir-mysql := $(clc-mysql)/db-mysql +sodir-mysql := usr/lib/clsql +clc-pg := $(clc-source)/clsql-postgresql +lispdir-pg := $(clc-pg)/db-postgresql +clc-pg-socket := $(clc-source)/clsql-postgresql-socket +lispdir-pg-socket := $(clc-pg-socket)/db-postgresql-socket +clc-aodbc := $(clc-source)/clsql-aodbc +lispdir-aodbc := $(clc-aodbc)/db-aodbc +clc-odbc := $(clc-source)/clsql-odbc +lispdir-odbc := $(clc-odbc)/db-odbc +clc-sqlite3 := $(clc-source)/clsql-sqlite3 +lispdir-sqlite3 := $(clc-sqlite3)/db-sqlite3 +clc-oracle := $(clc-source)/clsql-oracle +lispdir-oracle := $(clc-oracle)/db-oracle +clc-tests := $(clc-source)/clsql-tests +lispdir-tests := $(clc-tests)/tests + + +build-arch: build-arch-stamp +build-arch-stamp: + dh_testdir + $(MAKE) + touch build-arch-stamp + +build-indep: + +build: build-arch + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp + [ ! -f Makefile ] || $(MAKE) distclean + find . -type f -and -name \*~ -or -name .\*~ -exec rm -f {} \; + rm -rf doc/html + dh_clean db-mysql/clsql_mysql.dll uffi/clsql_uffi.dll + +install: DH_OPTIONS= install-indep install-arch + +install-indep: + dh_testdir + dh_testroot + dh_prep + dh_installdirs + dh_installdirs --all $(clc-source) + dh_installdirs -p $(pkg) $(lispdir-sql) etc + dh_installdirs -p $(pkg-pg) $(lispdir-pg) + dh_installdirs -p $(pkg-pg-socket) $(lispdir-pg-socket) + dh_installdirs -p $(pkg-aodbc) $(lispdir-aodbc) + dh_installdirs -p $(pkg-odbc) $(lispdir-odbc) + dh_installdirs -p $(pkg-sqlite3) $(lispdir-sqlite3) + dh_installdirs -p $(pkg-oracle) $(lispdir-oracle) + dh_installdirs -p $(pkg-tests) $(lispdir-tests) + + # Main package + dh_install -p $(pkg) $(srcs-sql) $(lispdir-sql) + dh_install -p $(pkg) clsql.asd $(clc-clsql) + dh_install -p $(pkg) debian/clsql-init.lisp etc + + dh_install -p $(pkg-pg) $(srcs-pg) $(lispdir-pg) + dh_install -p $(pkg-pg) clsql-postgresql.asd $(clc-pg) + dh_install -p $(pkg-pg-socket) $(srcs-pg-socket) $(lispdir-pg-socket) + dh_install -p $(pkg-pg-socket) clsql-postgresql-socket.asd $(clc-pg-socket) + dh_install -p $(pkg-aodbc) $(srcs-aodbc) $(lispdir-aodbc) + dh_install -p $(pkg-aodbc) clsql-aodbc.asd $(clc-aodbc) + dh_install -p $(pkg-odbc) $(srcs-odbc) $(lispdir-odbc) + dh_install -p $(pkg-odbc) clsql-odbc.asd $(clc-odbc) + dh_install -p $(pkg-sqlite3) $(srcs-sqlite3) $(lispdir-sqlite3) + dh_install -p $(pkg-sqlite3) clsql-sqlite3.asd $(clc-sqlite3) + dh_install -p $(pkg-oracle) $(srcs-oracle) $(lispdir-oracle) + dh_install -p $(pkg-oracle) clsql-oracle.asd $(clc-oracle) + + dh_install -p $(pkg-tests) $(srcs-tests) $(lispdir-tests) + dh_install -p $(pkg-tests) clsql-tests.asd $(clc-tests) + + # Documentation + rm -rf doc/html + (cd doc; tar xzf html.tar.gz; cd ..) + +install-arch: + dh_testdir + dh_testroot + dh_prep + dh_installdirs + + dh_installdirs -p $(pkg-uffi) $(lispdir-uffi) $(sodir-uffi) + dh_installdirs -p $(pkg-mysql) $(lispdir-mysql) $(sodir-mysql) + + # UFFI + dh_install -p $(pkg-uffi) $(srcs-uffi) $(lispdir-uffi) + dh_install -p $(pkg-uffi) $(srcs-uffi-so) $(sodir-uffi) + dh_install -p $(pkg-uffi) clsql-uffi.asd $(clc-uffi) + + # Backends + dh_install -p $(pkg-mysql) $(srcs-mysql) $(lispdir-mysql) + dh_install -p $(pkg-mysql) clsql-mysql.asd $(clc-mysql) + dh_install -p $(pkg-mysql) $(srcs-mysql-so) $(sodir-mysql) + +binary-indep: install-indep + dh_testdir -i + dh_testroot -i + dh_installdocs -i -p $(pkg) doc/html doc/clsql.pdf + dh_installexamples -i -p $(pkg) examples/* + dh_installchangelogs -i + dh_lisp -i + dh_compress -i + dh_fixperms -i + dh_installdeb -i + dh_gencontrol -i + dh_md5sums -i + dh_builddeb -i + +binary-arch: install-arch + dh_testdir -a + dh_testroot -a + dh_installdocs -a + dh_installchangelogs -a + dh_lisp -a + dh_strip -a + dh_compress -a + dh_fixperms -a + dh_installdeb -a + dh_shlibdeps -a + dh_gencontrol -a + dh_md5sums -a + dh_builddeb -a + +binary: binary-indep binary-arch + + +.PHONY: build clean binary-indep binary-arch binary install diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..1b92d46 --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=3 +http://files.kpe.io/clsql/clsql-([\d\.]*)\.tar\.gz debian uupdate -- cgit v1.2.3 From 07100f82e4e25d43b6bd0ab1021dfb2d2c7a8183 Mon Sep 17 00:00:00 2001 From: "Kevin M. Rosenberg" Date: Sat, 29 Oct 2022 06:30:27 +0200 Subject: Import cl-sql_6.7.2.orig.tar.gz [dgit import orig cl-sql_6.7.2.orig.tar.gz] --- BUGS | 30 + CONTRIBUTORS | 31 + ChangeLog | 3223 ++++++++++++++++++++ INSTALL | 5 + LATEST-TEST-RESULTS | 81 + LICENSE | 23 + Makefile | 50 + Makefile.common | 44 + NEWS | 47 + README | 43 + TODO | 27 + clsql-aodbc.asd | 37 + clsql-cffi.asd | 27 + clsql-db2.asd | 40 + clsql-mysql.asd | 92 + clsql-odbc.asd | 39 + clsql-oracle.asd | 40 + clsql-postgresql-socket.asd | 38 + clsql-postgresql-socket3.asd | 41 + clsql-postgresql.asd | 37 + clsql-sqlite.asd | 36 + clsql-sqlite3.asd | 37 + clsql-tests.asd | 60 + clsql-uffi.asd | 38 + clsql.asd | 110 + db-aodbc/Makefile | 6 + db-aodbc/aodbc-package.lisp | 28 + db-aodbc/aodbc-sql.lisp | 102 + db-db2/Makefile | 23 + db-db2/db2-api.lisp | 110 + db-db2/db2-constants.lisp | 26 + db-db2/db2-loader.lisp | 52 + db-db2/db2-objects.lisp | 15 + db-db2/db2-package.lisp | 23 + db-db2/db2-sql.lisp | 70 + db-db2/foreign-resources.lisp | 57 + db-mysql/Makefile | 85 + db-mysql/Makefile.msvc | 40 + db-mysql/clsql_mysql.c | 157 + db-mysql/mysql-api.lisp | 559 ++++ db-mysql/mysql-client-info.lisp | 50 + db-mysql/mysql-loader.lisp | 51 + db-mysql/mysql-objects.lisp | 25 + db-mysql/mysql-package.lisp | 153 + db-mysql/mysql-sql.lisp | 848 +++++ db-mysql/testing/mysql-struct-size.cc | 10 + db-mysql/testing/mysql-struct-size.lisp | 11 + db-odbc/Makefile | 24 + db-odbc/odbc-api.lisp | 1023 +++++++ db-odbc/odbc-constants.lisp | 979 ++++++ db-odbc/odbc-dbi.lisp | 706 +++++ db-odbc/odbc-ff-interface.lisp | 418 +++ db-odbc/odbc-loader.lisp | 41 + db-odbc/odbc-package.lisp | 69 + db-odbc/odbc-sql.lisp | 163 + db-oracle/Makefile | 23 + db-oracle/README | 21 + db-oracle/foreign-resources.lisp | 57 + db-oracle/oracle-api.lisp | 356 +++ db-oracle/oracle-constants.lisp | 541 ++++ db-oracle/oracle-loader.lisp | 53 + db-oracle/oracle-objects.lisp | 128 + db-oracle/oracle-package.lisp | 23 + db-oracle/oracle-sql.lisp | 1074 +++++++ db-postgresql-socket/Makefile | 6 + db-postgresql-socket/postgresql-socket-api.lisp | 973 ++++++ .../postgresql-socket-package.lisp | 58 + db-postgresql-socket/postgresql-socket-sql.lisp | 346 +++ db-postgresql-socket3/api.lisp | 64 + db-postgresql-socket3/package.lisp | 35 + db-postgresql-socket3/sql.lisp | 328 ++ db-postgresql/Makefile | 6 + db-postgresql/postgresql-api.lisp | 302 ++ db-postgresql/postgresql-loader.lisp | 40 + db-postgresql/postgresql-package.lisp | 87 + db-postgresql/postgresql-sql.lisp | 448 +++ db-sqlite/Makefile | 23 + db-sqlite/sqlite-api.lisp | 322 ++ db-sqlite/sqlite-loader.lisp | 40 + db-sqlite/sqlite-package.lisp | 21 + db-sqlite/sqlite-sql.lisp | 332 ++ db-sqlite3/sqlite3-api.lisp | 367 +++ db-sqlite3/sqlite3-loader.lisp | 37 + db-sqlite3/sqlite3-methods.lisp | 20 + db-sqlite3/sqlite3-package.lisp | 21 + db-sqlite3/sqlite3-sql.lisp | 353 +++ doc/COPYING.GFDL | 330 ++ doc/Makefile | 148 + doc/README | 24 + doc/TODO | 31 + doc/appendix.xml | 884 ++++++ doc/bookinfo.xml | 64 + doc/catalog-darwin.xml | 43 + doc/catalog-debian.xml | 43 + doc/catalog-mandrake.xml | 43 + doc/catalog-redhat.xml | 43 + doc/catalog-suse.xml | 43 + doc/catalog-suse91.xml | 48 + doc/clsql.pdf | Bin 0 -> 795724 bytes doc/clsql.xml | 30 + doc/csql.xml | 749 +++++ doc/entities.inc | 31 + doc/fo.xsl | 6 + doc/global-index.xml | 137 + doc/glossary.xml | 197 ++ doc/html.tar.gz | Bin 0 -> 128401 bytes doc/html.xsl | 10 + doc/html_chunk.xsl | 9 + doc/intro.xml | 265 ++ doc/mysql-macosx-notes.txt | 60 + doc/preface.xml | 17 + doc/ref-clsql-sys.xml | 103 + doc/ref-clsql.xml | 2416 +++++++++++++++ doc/ref-conditions.xml | 813 +++++ doc/ref-connect.xml | 2364 ++++++++++++++ doc/ref-fddl.xml | 2618 ++++++++++++++++ doc/ref-fdml.xml | 2116 +++++++++++++ doc/ref-lob.xml | 273 ++ doc/ref-ooddl.xml | 1149 +++++++ doc/ref-oodml.xml | 1078 +++++++ doc/ref-prepared.xml | 298 ++ doc/ref-recording.xml | 825 +++++ doc/ref-syntax.xml | 1058 +++++++ doc/ref-transaction.xml | 844 +++++ doc/schemas.xml | 24 + doc/threading-warnings.txt | 77 + doc/xinclude.mod | 24 + examples/clsql-tutorial.lisp | 196 ++ examples/dot.clsql-test.config | 14 + examples/run-tests.sh | 78 + examples/sqlite3/init-func/Makefile | 21 + examples/sqlite3/init-func/example.lisp | 68 + examples/sqlite3/init-func/iso-8859-15-coll.c | 77 + notes/add-type-hints.txt | 32 + sql/Makefile | 24 + sql/ansi-loop.lisp | 2282 ++++++++++++++ sql/base-classes.lisp | 57 + sql/cmucl-compat.lisp | 101 + sql/command-object.lisp | 73 + sql/conditions.lisp | 170 ++ sql/database.lisp | 363 +++ sql/db-interface.lisp | 500 +++ sql/decimals.lisp | 419 +++ sql/expressions.lisp | 1247 ++++++++ sql/fddl.lisp | 437 +++ sql/fdml.lisp | 515 ++++ sql/generic-odbc.lisp | 263 ++ sql/generic-postgresql.lisp | 429 +++ sql/generics.lisp | 212 ++ sql/initialize.lisp | 61 + sql/kmr-mop.lisp | 101 + sql/loop-extension.lisp | 247 ++ sql/metaclasses.lisp | 641 ++++ sql/ooddl.lisp | 248 ++ sql/oodml.lisp | 1353 ++++++++ sql/operations.lisp | 262 ++ sql/package.lisp | 621 ++++ sql/pool.lisp | 185 ++ sql/recording.lisp | 165 + sql/sequences.lisp | 103 + sql/syntax.lisp | 198 ++ sql/time.lisp | 1359 +++++++++ sql/transaction.lisp | 152 + sql/utils.lisp | 511 ++++ tests/Makefile | 24 + tests/README | 124 + tests/benchmarks.lisp | 87 + tests/datasets.lisp | 141 + tests/ds-artists.lisp | 31 + tests/ds-employees.lisp | 405 +++ tests/ds-nodes.lisp | 118 + tests/package.lisp | 33 + tests/test-basic.lisp | 314 ++ tests/test-connection.lisp | 80 + tests/test-fddl.lisp | 454 +++ tests/test-fdml.lisp | 780 +++++ tests/test-i18n.lisp | 52 + tests/test-init.lisp | 399 +++ tests/test-internal.lisp | 77 + tests/test-ooddl.lisp | 202 ++ tests/test-oodml.lisp | 1242 ++++++++ tests/test-pool.lisp | 83 + tests/test-syntax.lisp | 465 +++ tests/test-time.lisp | 466 +++ tests/utils.lisp | 100 + uffi/Makefile | 77 + uffi/Makefile.32+64bits | 100 + uffi/Makefile.msvc | 31 + uffi/clsql-uffi-loader.lisp | 52 + uffi/clsql-uffi-package.lisp | 32 + uffi/clsql-uffi.lisp | 167 + uffi/clsql_uffi.c | 73 + uffi/clsql_uffi.lib | Bin 0 -> 1658 bytes uffi/ptrbits.c | 11 + 194 files changed, 56045 insertions(+) create mode 100644 BUGS create mode 100644 CONTRIBUTORS create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 LATEST-TEST-RESULTS create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.common create mode 100644 NEWS create mode 100644 README create mode 100644 TODO create mode 100644 clsql-aodbc.asd create mode 100644 clsql-cffi.asd create mode 100644 clsql-db2.asd create mode 100644 clsql-mysql.asd create mode 100644 clsql-odbc.asd create mode 100644 clsql-oracle.asd create mode 100644 clsql-postgresql-socket.asd create mode 100644 clsql-postgresql-socket3.asd create mode 100644 clsql-postgresql.asd create mode 100644 clsql-sqlite.asd create mode 100644 clsql-sqlite3.asd create mode 100644 clsql-tests.asd create mode 100644 clsql-uffi.asd create mode 100644 clsql.asd create mode 100644 db-aodbc/Makefile create mode 100644 db-aodbc/aodbc-package.lisp create mode 100644 db-aodbc/aodbc-sql.lisp create mode 100644 db-db2/Makefile create mode 100644 db-db2/db2-api.lisp create mode 100644 db-db2/db2-constants.lisp create mode 100644 db-db2/db2-loader.lisp create mode 100644 db-db2/db2-objects.lisp create mode 100644 db-db2/db2-package.lisp create mode 100644 db-db2/db2-sql.lisp create mode 100644 db-db2/foreign-resources.lisp create mode 100644 db-mysql/Makefile create mode 100755 db-mysql/Makefile.msvc create mode 100644 db-mysql/clsql_mysql.c create mode 100644 db-mysql/mysql-api.lisp create mode 100644 db-mysql/mysql-client-info.lisp create mode 100644 db-mysql/mysql-loader.lisp create mode 100644 db-mysql/mysql-objects.lisp create mode 100644 db-mysql/mysql-package.lisp create mode 100644 db-mysql/mysql-sql.lisp create mode 100644 db-mysql/testing/mysql-struct-size.cc create mode 100644 db-mysql/testing/mysql-struct-size.lisp create mode 100644 db-odbc/Makefile create mode 100644 db-odbc/odbc-api.lisp create mode 100644 db-odbc/odbc-constants.lisp create mode 100644 db-odbc/odbc-dbi.lisp create mode 100644 db-odbc/odbc-ff-interface.lisp create mode 100644 db-odbc/odbc-loader.lisp create mode 100644 db-odbc/odbc-package.lisp create mode 100644 db-odbc/odbc-sql.lisp create mode 100644 db-oracle/Makefile create mode 100644 db-oracle/README create mode 100644 db-oracle/foreign-resources.lisp create mode 100644 db-oracle/oracle-api.lisp create mode 100644 db-oracle/oracle-constants.lisp create mode 100644 db-oracle/oracle-loader.lisp create mode 100644 db-oracle/oracle-objects.lisp create mode 100644 db-oracle/oracle-package.lisp create mode 100644 db-oracle/oracle-sql.lisp create mode 100644 db-postgresql-socket/Makefile create mode 100644 db-postgresql-socket/postgresql-socket-api.lisp create mode 100644 db-postgresql-socket/postgresql-socket-package.lisp create mode 100644 db-postgresql-socket/postgresql-socket-sql.lisp create mode 100644 db-postgresql-socket3/api.lisp create mode 100644 db-postgresql-socket3/package.lisp create mode 100644 db-postgresql-socket3/sql.lisp create mode 100644 db-postgresql/Makefile create mode 100644 db-postgresql/postgresql-api.lisp create mode 100644 db-postgresql/postgresql-loader.lisp create mode 100644 db-postgresql/postgresql-package.lisp create mode 100644 db-postgresql/postgresql-sql.lisp create mode 100644 db-sqlite/Makefile create mode 100644 db-sqlite/sqlite-api.lisp create mode 100644 db-sqlite/sqlite-loader.lisp create mode 100644 db-sqlite/sqlite-package.lisp create mode 100644 db-sqlite/sqlite-sql.lisp create mode 100644 db-sqlite3/sqlite3-api.lisp create mode 100644 db-sqlite3/sqlite3-loader.lisp create mode 100644 db-sqlite3/sqlite3-methods.lisp create mode 100644 db-sqlite3/sqlite3-package.lisp create mode 100644 db-sqlite3/sqlite3-sql.lisp create mode 100644 doc/COPYING.GFDL create mode 100644 doc/Makefile create mode 100644 doc/README create mode 100644 doc/TODO create mode 100644 doc/appendix.xml create mode 100644 doc/bookinfo.xml create mode 100644 doc/catalog-darwin.xml create mode 100644 doc/catalog-debian.xml create mode 100644 doc/catalog-mandrake.xml create mode 100644 doc/catalog-redhat.xml create mode 100644 doc/catalog-suse.xml create mode 100644 doc/catalog-suse91.xml create mode 100644 doc/clsql.pdf create mode 100644 doc/clsql.xml create mode 100644 doc/csql.xml create mode 100644 doc/entities.inc create mode 100644 doc/fo.xsl create mode 100644 doc/global-index.xml create mode 100644 doc/glossary.xml create mode 100644 doc/html.tar.gz create mode 100644 doc/html.xsl create mode 100644 doc/html_chunk.xsl create mode 100644 doc/intro.xml create mode 100644 doc/mysql-macosx-notes.txt create mode 100644 doc/preface.xml create mode 100644 doc/ref-clsql-sys.xml create mode 100644 doc/ref-clsql.xml create mode 100644 doc/ref-conditions.xml create mode 100644 doc/ref-connect.xml create mode 100644 doc/ref-fddl.xml create mode 100644 doc/ref-fdml.xml create mode 100644 doc/ref-lob.xml create mode 100644 doc/ref-ooddl.xml create mode 100644 doc/ref-oodml.xml create mode 100644 doc/ref-prepared.xml create mode 100644 doc/ref-recording.xml create mode 100644 doc/ref-syntax.xml create mode 100644 doc/ref-transaction.xml create mode 100644 doc/schemas.xml create mode 100644 doc/threading-warnings.txt create mode 100644 doc/xinclude.mod create mode 100644 examples/clsql-tutorial.lisp create mode 100644 examples/dot.clsql-test.config create mode 100755 examples/run-tests.sh create mode 100644 examples/sqlite3/init-func/Makefile create mode 100644 examples/sqlite3/init-func/example.lisp create mode 100644 examples/sqlite3/init-func/iso-8859-15-coll.c create mode 100644 notes/add-type-hints.txt create mode 100644 sql/Makefile create mode 100644 sql/ansi-loop.lisp create mode 100644 sql/base-classes.lisp create mode 100644 sql/cmucl-compat.lisp create mode 100644 sql/command-object.lisp create mode 100644 sql/conditions.lisp create mode 100644 sql/database.lisp create mode 100644 sql/db-interface.lisp create mode 100644 sql/decimals.lisp create mode 100644 sql/expressions.lisp create mode 100644 sql/fddl.lisp create mode 100644 sql/fdml.lisp create mode 100644 sql/generic-odbc.lisp create mode 100644 sql/generic-postgresql.lisp create mode 100644 sql/generics.lisp create mode 100644 sql/initialize.lisp create mode 100644 sql/kmr-mop.lisp create mode 100644 sql/loop-extension.lisp create mode 100644 sql/metaclasses.lisp create mode 100644 sql/ooddl.lisp create mode 100644 sql/oodml.lisp create mode 100644 sql/operations.lisp create mode 100644 sql/package.lisp create mode 100644 sql/pool.lisp create mode 100644 sql/recording.lisp create mode 100644 sql/sequences.lisp create mode 100644 sql/syntax.lisp create mode 100644 sql/time.lisp create mode 100644 sql/transaction.lisp create mode 100644 sql/utils.lisp create mode 100644 tests/Makefile create mode 100644 tests/README create mode 100644 tests/benchmarks.lisp create mode 100644 tests/datasets.lisp create mode 100644 tests/ds-artists.lisp create mode 100644 tests/ds-employees.lisp create mode 100644 tests/ds-nodes.lisp create mode 100644 tests/package.lisp create mode 100644 tests/test-basic.lisp create mode 100644 tests/test-connection.lisp create mode 100644 tests/test-fddl.lisp create mode 100644 tests/test-fdml.lisp create mode 100644 tests/test-i18n.lisp create mode 100644 tests/test-init.lisp create mode 100644 tests/test-internal.lisp create mode 100644 tests/test-ooddl.lisp create mode 100644 tests/test-oodml.lisp create mode 100644 tests/test-pool.lisp create mode 100644 tests/test-syntax.lisp create mode 100644 tests/test-time.lisp create mode 100644 tests/utils.lisp create mode 100644 uffi/Makefile create mode 100644 uffi/Makefile.32+64bits create mode 100644 uffi/Makefile.msvc create mode 100644 uffi/clsql-uffi-loader.lisp create mode 100644 uffi/clsql-uffi-package.lisp create mode 100644 uffi/clsql-uffi.lisp create mode 100644 uffi/clsql_uffi.c create mode 100644 uffi/clsql_uffi.lib create mode 100644 uffi/ptrbits.c diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..07702a6 --- /dev/null +++ b/BUGS @@ -0,0 +1,30 @@ +1. SBCL/Postgresql Interaction + +Gabor Melis reported an interaction with SBCL and the postgresql +database backend using a multithreaded program and Portable +AllegroServe. It appears that libpg installs its own SIGPIPE +handler. When a multithreaded program servicing socket requests has +SIGPIPE conditions thrown, the running SBCL instance is killed. The +web page http://linux.com.hk/docs/postgresql/libpq-threading.html has +more information about libpq's SIGPIPE handler. + +2. Object joining + +Aleksandar Bakic reported the documentation says that :home-key and +:foreign-key properties of the :db-info property may contain lists of +symbols. However, update-objects-joins fails in such cases. + +2. configure file +Automatically generate makefiles based on the configuration of an +end-users system + +3. SBCL/MySQL interaction Similar to the postgres interaction noted +above SBCL installs its own SIGPIPE handler but the mysql library +disables it breaking thread interrupts. See also +http://ccl.clozure.com/irc-logs/lisp/2012-02/lisp-2012.02.22.txt Look +for the conversation starting at 20:03:32 between bobbysmith007 and +pkhuong. + +4. Thread safety issues. While clsql attempts to be threadsafe there +are some significant issues with some backends. See +doc/threading-warnings.txt for more info. diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..19c5799 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,31 @@ +CLSQL Contributors +------------------ +Kevin Rosenberg (main author CLSQL) +Marcus Pearce (initial port UncommonSQL, co-developer of CLSQL version 3.0) +Pierre Mai (original author MaiSQL from which CLSQL was based) +Aurelio Bignoli (SQLite backend) +Marc Battyani (Large object support for postgresql, initial connection pool code) +Ryan Davis, Nathan Bird, & Russ Tyndall (sponsored by http://www.acceleration.net/programming/) +Victor (vityok@github), sqlite3 backend updates and clsql_uffi long-long support +Aaron Burrow, clsql_uffi unsigned integer bugs +Ilya Khaprov deadtrickster@github - mysql backend stored-procedure / multiple result set support + + + + +USQL Contributors +----------------- +This is a list of those individuals who have contributed in some way +or other to Uncommonsql. The sources of the attributions are CVS +annotation, patch submission, and original authorship, write us if +we've missed anybody. + +Jesse Bouwman +Craig Brozefsky +Sean Champ +Matthew Danish +Adam Di Carlo +Lyn Headley +John Krug +Pierre Mai (original author) +Christopher J. Vogt diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..a66a94a --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3223 @@ +2022-10-28 Kevin Rosenberg + * Version 6.7.2 release + * LICENSE: After consulting with the contributors to CLSQL, + CLSQL is now relicensed under MIT license + +2016-01-26 Kevin Rosenberg + * Version 6.7.0 release + * sql/utils.lisp: Apply patch from Martin Simmons for + Lispworks 7 compatiblity + +2016-01-17 Russ Tyndall + * applied patches from Javeier Olaechea + * allows unix socket connections in clsql-postgressql-socket3 + * modernize asd slightly + +2015-10-09 Russ Tyndall + * add decimals.lisp file https://github.com/tlikonen/cl-decimals + * use this for safe parsing of numeric / decimal / rational types + * Added because newer postgres print money types as currency strings + +2015-08-12 Kevin Rosenberg + * Version 6.6.3 release + * db-oracle/oracle-sql.lisp: Patch for PostgreSQL socket interface + for unicode characters. Thanks to Jason Melbye. + +2015-06-02 Daniel KochmaÅ„ski + * clsql.asd, sql/package.lisp: Add ECL compatibility fixes + * sql/db-interface.lisp: Fix declaration typo + +2015-04-06 Russ Tyndall + * sql/operations, sql/expressions: add postgresql E-string + operator / expression. Needed for correct regex handling + EG: [E "some string"]=> E'some string' + +2015-03-30 Kevin Rosenberg + * Version 6.6.2 release + * db-oracle/oracle-sql.lisp: Remove extra hyphen, thanks to + Thomas Vossen + +2015-03-24 Russ Tyndall + * sql/oodml.lisp: fixed call-next-method in the base of + read-sql-value and replaced with a continuable + sql-value-conversion-error + * default read-sql-value for list + * tests for sql-value-conversion-errors and list + +2015-03-18 Russ Tyndall + * {uffi,db-mysql}/Makefile: remove -pie build hardening for + which caused load issues for Linux Mint + +2015-03-18 Kevin Rosenberg + * Version 6.6.1 release + * {uffi,db-mysql}/Makefile: Remove pie from build + hardening options for Debian/Ubuntu systems. Thanks to + DJ and Russ Tyndall + +2015-02-26 Kevin Rosenberg + * Version 6.6.0 release + * {uffi,db-mysql}/Makefile: Add build hardening for Debian + +2015-02-24 Russ Tyndall + * mysql-sql.lisp + an error in type declarations generating a compilation warning + was being treated as an error in recent SBCLs, fixed the type + warning by correcting the type (still a ton of compliation + notes) + +2015-02-23 Russ Tyndall + * sql/metaclasses.lisp + made reinitialize-instance return the instance passed to it as + SBCL now expected (mentioned on the SBCL-devel mailing list by + Stas + +2014-12-03 Russ Tyndall + * sqlite-sql.lisp + Added database arg to `canonicalize-result-types` so that it could + correctly call `sqlite-aref` with the required number of arguments + Thanks Zach Beane for the bug report. + +2014-07-29 Russ Tyndall + * mysql-api.lisp, mysql-sql.lisp, test-connection.lisp + Added code to the mysql backend to pull all result sets as + multiple args. This fixes a bug in the mysql backend where trying + to query after executing a stored procedure (even on a pooled + connection) would raise an error about the connection being out of + sync. The second result set for the stored procedure seems to be + empty, so not sure why we need to iterate past it. + + patch / bugreport provided by: Ilya Khaprov deadtrickster@github + +2014-06-11 Russ Tyndall + * databases.lisp, sqlite3-sql.lisp + Similar to and overriding the patch 2014-01-30 937a3d, adds a + default-encoding variable uses that in places where a nil encoding + was being passed. Defaults to :utf-8. This is mostly in place so + that uffi and cffi both work similarly (by moving the default into + clsql instead of clsql-uffi). This allows my automated build + environment to do its job + +2014-06-10 Russ Tyndall + + * db-mysql/Makefile - the results of dpkg-buildflags --get LDFLAGS + seem to have changed on my system and and sed was returning + invalid command line arguments to ld. To resolve this I changed + sed to emit valid args, which seems to have resolved the issue. + +2014-04-24 Russ Tyndall + * oodml.lisp, test-oodml.lisp Better handling of view-slots of + type symbol/keyword. Better handling of printing and reading + bindings (per mailing list request, always read and write in base + 10) + +2014-03-04 Kevin Rosenberg + * Version 6.5.0: New release + * makefile.common: Check for /usr/bin/dpkg-buildflags + * {db-mysql,uffi}/Makefile: Use debian buildflags + +2014-02-24 Russ Tyndall + * oodml.lisp bind *print-length* to nil before printing + lists/arrays to the database. + +2014-01-30 Russ Tyndall + * sqlite3-sql.lisp specify :utf-8 as the default encoding if there + is not one (allows :clsql-cffi to be closer to working for this + backend). + + I ran the test suite successfully once with :clsql-cffi, but there + after I got spurious errors and especially unrecoverable errors + while connecting about the database being locked + +2014-01-30 Russ Tyndall + * sqlite3-sql.lisp, fddl.lisp Dont compare database-identifiers + with invalid comparison operators + +2014-01-30 Russ Tyndall + * generic-odbc.lisp, ooddl.lisp, generic-postgresql.lisp, + test-init.lisp, ds-nodes.lisp, generic-odbc.lisp, odbc-sql.lisp + + auto-increment-column support improvement (mssql esp, now will + auto-fill after insert). Use +auto-increment-names+ to determine + auto-increment-column-p. + + This triggered much test failing as regards normalized classes / + autoincrement primary key stuff. + + New odbc-postgresql-database sub-type + + POSSIBLY BREAKING CHANGES: + 1 ) Previously all classes in a normalized heirachy had their p-key + marked as "auto-increment". Usually auto-increment means a key + supplied by the database system, so this was decidedly + non-standard usage (clsql is explicitly providing the key for all + normalized subclasses of any given parent see ds-nodes.lisp). Some + RDMS will not allow insertion/updates of autoincrement columns + without hoop jumping and, as it doesnt really make much sense, I + removed the "auto-increment" aspects of normalized sub-classes. + Now the primary keys are chained regardless. The parent-most key + can be autoincrement or not. + + 2 ) ODBC Postgresql connections are now both GENERIC-ODBC-DATABASE + and GENERIC-POSTGRESQL-DATABASE. Probably not a widely used path, + but this change allows most of the previously failing tests to + pass on this backend (we now format stuff correctly for postgres). + I anticipate this probably is not perfect yet (IE: I probably + missed something) + +2014-01-29 Russ Tyndall + * oodml.lisp, generics.lisp - added + clsql-sys::view-classes-and-storable-slots generic (added method + previously). Also added to-database-p keyword to allow overrides + to distinguish between the two situations. Mostly so that + clsql-helper:dirty-slots-mixin can filter slots when writing + values to the database but still allow all slots to be read from + the database + +2014-01-17 Russ Tyndall + * oodml.lisp, generics.lisp - added filter-select-list generic + to allow fine grained control of generated query/object mappings + +2014-01-07 Russ Tyndall + * clsql-uffi.lisp, sqlite3 auto-increment support + * clsql-uffi.lisp, test-basic.lisp, fixes related to unsigned vs + signed ints (thanks Aaron Burrow) + * cleaning and testing + +2013-09-27 Russ Tyndall + * fixed bug converting to boolean in db-mysql/mysql-sql.lisp + from github user Sectoid https://github.com/UnwashedMeme/clsql/pull/1 + +2013-06-19 Russ Tyndall + * sql/oodml.lisp, db-postgresql-socket3/sql.lisp, + db-mysql/mysql-objects.lisp, sql/generic-odbc.lisp + Refactored read-sql-value similar to the other recent refactorings + + * the symbol case now uses intern instead of read-from-string + (which may not return a symbol and could have security issues + since read-eval was not being unset) + + * read-eval is now off for all cases + + * centralized logic into a single case statement, hopefully making + this more readable and debuggable + + * TODO: make these refactorings to the oracle backend (I cannot + test against oracle and am loathe to change without testing + +2013-06-19 Russ Tyndall + * sql/mysql-objects.lisp + Found and refactored a way some more eql specified methods of + database-get-type-specifier in mysql + +2013-06-18 Russ Tyndall + * sql/oodml.lisp, sql/mysql-objects.lisp + refactored database-output-sql-as-type in a similar fashion to + the previous refactor of database-get-type-specifier (fewer + methods using case instead of eql specifiers) + + * removed very strange definition of outputing floats as strings + for something sane (it was previously doing silly work like + setting the default read float type (which AFAICT doesnt affect + printing)) + + * half of the cases nil returned "" other times it returned nil, + now if we get a null value we return nil always + + * removed odd-logic (seemingly untouched since the initial import), + that removed null characters from printed lists. If we have #\null + in a printed list, we had probably better figure out what went wrong + there rather than destructively modifying the list output on the way + to the DB ;; removed (substitute-char-string escaped #\Null " ") + +2013-06-18 Russ Tyndall + * sql/generic-odbc.lisp, sql/generic-postgresql.lisp, sql/oodml.lisp + tests/test-fddl.lisp + + refactored database-get-type-specifier for postgres and mssql + + Single methods with a case on the symbol arg (similar to the recent + refactoring in oodml.lisp) + + This reduces line count and generally makes it easier to find and + read all the backend-specific types + +2013-06-10 Russ Tyndall + * sql/oodml.lisp, sql/generic-postgresql.lisp, doc/ref-fddl.xml, + sql/packages.lisp + + Updated get-database-type-specifier to handle text/longchar type + and refactored + + * added a warning above defaulting to VARCHAR (since its probably + NOT what is expected on a bad type specifier). + + * added a case where the specified type being a string, passes + that string directly (to better/more easily allow db-specific + data-types). + + * added cases where longchar or text converts to text, and + exported those symbols (as this seemed type seemed to be missing + from fddl/oddl anyway). + + * reorganized these default methods into a single method with a + case statement rather than many eql specified methods (about half + the code) + + * updated the docs to use text instead of longchar since text is + a more standard db-type (pg,my,and ms all use text) + +2013-11-23 Kiss Kalman + * utils/sql.lisp: Commit patch adding ccl getenv support + +2013-04-17 Kevin Rosenberg + * Version 6.4.1 + * sql/utils.lisp: Patch from Ben Hyde to add weak hash table + support for CCL. + +2013-03-07 Ryan Davis + * db-postgresql-socket/postgresql-socket-api.lisp - bugfix to + adapt to changes in md5:md5sum-sequence. CLSQL now requires a + version of MD5 released on or after 2012-11-25 (the latest version + currenty in quicklisp). Thanks to Nicolas Neuss for the bug + report. + * db-odbc/odbc-sql.lisp - keep a reference to the original + connection spec used to create `odbc-database` connections + +2013-01-09 Russ Tyndall + sql/oodml.lisp - changed view-classes-and-storable-slots to a + method (as it was intended to be all along) + +2012-12-19 Kevin Rosenberg + * Version 6.4 + +2012-11-20 Russ Tyndall + ## Large refactoring of sql/oodml.lisp and surrounding code + * cleaned up update-records-from-* to utilize a single codepath + previously there was much duplicate code + * tried to further unify direct/effective slot-defs + * cleaned up much of the normalized classes code to be more + consistent in how it operated, and the code it used (less + copy/paste more functions) + * tried to standardize iteration a bit, previously almost all + of the iteration constructs in CL were used. Tried to + standardize on loop for readability / cohesiveness + * made functions to more easily look up particular slots, since + this was being done differently in each function + * added more doc-strings and updated documentation about + normalized classes + * inner-joins that are referenced in where clauses are no longer + added to the select table list (since they are also referenced + in the join position and this led to sql errors) + * collect-table-references methods added for sql-ident-table and + list + + ## Semantic changes + * disallow where-less updates from objects (ie cannot call + update-records-from* with a keyless object) + * ordered-class-direct-slots now returns direct-slot-definitions + instead of effective-slot-definitions (as per name) + * direct-slot-definitions now contain the db-info hash table (same + as effective slots) + * removed this-class arg from update-instance-from-records - used to + be used for normalized code, no longer needed for that. + * find-all - bug fixes in table references, previously where clauses + surrounded by a list would have none of their references added to + the select. This was being exploited by certain code paths. Now + all where clauses are searched + - No longer includes order-by and distinct as columns in the select + list. The values seemed to be ignored in every code path and distinct + seemed to be intended to be used as a boolean anyway + + +2012-11-20 Nathan Bird + + * update-objects-joins - changed the default of slot from t (which + used to mean :deferred) to :immediate (to match the default + behavior of other parts of the system). It also seemed like a bad + default to pull all the slots that were explicitly specified to be + not pulled by default. This function now accepts more special + values (:immediate, :deferred, :all, or a list of slots). To get + the old behavior call with :slots :deferred. + + +2012-10-30 Russ Tyndall + * sql/command-object.lisp - added dates/times to the parameter value + coersion and pulled this into a new generic prepare-sql-parameter + +2012-09-04 Kevin Rosenberg + * Version 6.3 released + +2012-09-04 Russ Tyndall + * sql/expressions.lisp - Try to respect the casing of symbols + where it seems intentional (ie: is not default). This should fix + a failing test case, and I think behaves more understandibly. + + If you specify a casing '|Foo Bar| lets treat that a string "Foo Bar" + and output it escaped + +2012-08-28 Ryan Davis + * db-sqlite3/sqlite3-api.lisp - allow pathnames in the connection + settings, so '("/db/my.sqlite") and '(#P"/db/my.sqlite") are + equivalent. Updated the docs to match. + +2012-08-17 Russ Tyndall + * db-postgresql-socket3/package.lisp - shadow + postgresql-notification for compatibility with new + cl-postgres (Thanks Zach) + +2012-07-09 Russ Tyndall + * sql/oodml.lisp - fixed a bug where the order by was being + destructively modified, causing odd caching issues when the + selected object was not statically known (eg unreferenced tables + could show up in the query if they were cached by a previous call + through this function. I replaced this code with a + non-destructive variant which should solve this. + + Thanks to Philipp Marek for the bug report + +2012-06-25 Russ Tyndall + * sql/util.lisp, sql/metaclasses.lisp + Dequote database-identifiers if needed (passed a quoted symbol) + Metaclass args come through unquoted, so this eases interactions + with them + +2012-06-22 Russ Tyndall + * sql/metaclasses.lisp: Changed compute-effective-slot-definition + to correctly copy the autoincrement-sequence slot to the ESD + previously it was being skipped (seemingly by accident). Thanks + to flip214 on #lisp for the bug report + +2012-04-26 Kevin Rosenberg + * Version 6.2 released: thanks to all the contributors! + +2012-04-25 Nathan Bird + + * doc/threading-warnings.txt: Adding some notes from J.T.Klein + about the current state of thread-safety in clsql. This should be + incorporated into the main docs at some point. + +2012-04-25 Russ Tyndall + * sql/expressions.lisp (output-sql): on rendering update/insert + expression values, ensure that *in-subselect* is bound to T + so that the values will be correctly paren delimited + +2012-04-24 Nathan Bird + + * sql/expressions.lisp (output-sql): on mysql CREATE TABLE + statements use 'ENGINE=innodb' instead of 'Type=InnoDB'. This has + apparently been preferred since mysql 4.1 and mysql 5.5 removed + type as a valid keyword. + +2012-03-28 Russ Tyndall + + * sql/sequences.lisp: [A Patch FROM 2011-07-28 changed sequences. + They were previously prefixed with _CLSQL_SEQ_ but are now + suffixed with _CLSQL_SEQ. This is likely to break existing + implementations using the default sequence names + + setting *old-sequence-names* to T, should force using the older + naming scheme + +2012-03-27 Ryan Davis + + * sql/expressions.lisp: Fixed bug with subqueries in the where + clause of update-records and delete-records generating invalid + SQL. Previously subselects in where clauses would not have enough + parentheses, for example: "WHERE Id IN SELECT foo_id FROM bar" + vs. "WHERE Id IN (SELECT foo_id FROM bar)" + * tests/test-syntax.lisp: Added tests for using subqueries in the + where clause in update-records and delete-records. Moved asserts + in the test-output-sql/sql-ident-table function into the standard + test framework. + * doc/appendix.xml: added :connection-string to the information on + ODBC connection specs, and added example code connecting to ODBC + databases. + +2012-01-05 Nathan Bird + + * db-odbc/odbc-dbi.lisp: handle sql decimal type in the same way + as numeric type-- read into a double float. + +2011-01-04 Russ Tyndall + * sql/operations.lisp + + Fixed bug reported by JTK related to the not-null sql-expression + especially as used in conjunction with the is operator. + + Made null called with more than one argument throw an exception + instead of silently discarding all arguments past the first + +2012-01-04 Nathan Bird + + * db-odbc/odbc-api.lisp (%sql-driver-connect): in the call to + odbc's SQLDriverConnect default the WindowHandle argument to a null ptr so + that connecting with :connection-string will work in the default + case of SQL_DRIVER_NOPROMPT. + + I.e. you can now do things like: + (clsql:connect '("DsnName" "UserName" "" :connection-string + "DRIVER={FreeTDS};SERVER=...;DATABASE=...;UID=...;PWD=...;PORT=1433;TDS_Version=8.0;APP=clsql") + :database-type :odbc) + + I believe the DsnName and Username at that point are only used when + printing the connection information. + +2011-12-20 Kevin Rosenberg + * Version 6.1.1 + * db-oracle/oracle.lisp: Typo correction (Elias Martenson) + +2011-12-19 Kevin Rosenberg + * Version 6.1.0 + * db-oracle/oracle.lisp: Change length function to + uffi:foreign-string-length to handle foreign encodings. + Thanks to Elias Martenson. + +2011-11-28 Russ Tyndall + * db-odbc/odbc-api.lisp, tests/test-time.lisp + + In ODBC backend, avoid going through the common lisp + universal-time type (because it lacks support for historic dates) + + *time-conversion-function* renamed to *time-format* + + Patch from: Francisco Vides Fernandez + +2011-10-18 Russ Tyndall + * db-odbc/odbc-api.lisp + + Added type specifier so MSSQL nvarchar fields can make it through + +2011-09-12 Russ Tyndall + * sql/fddl.lisp sql/generic-postgres.lisp db-mysql/mysql-sql.lisp + sql/generic-odbc.lisp sql/odbc-api.lisp sql/odbc-dbi.lisp + + Fix bugs in list-attribute(s|-types) where passing an escaped, + instead of unescaped column name, caused these functions to return + less data than they should have. + +2011-08-03 Kevin Rosenberg + * CLSQL 6.0.0 released + +2011-07-28 Russ Tyndall + + * db-postgresql-socket3/: Added a backend that utilized postgres + socket api version 3. Uses the cl-postgres project (from + postmodern) to handle this. Allows use of parameterized / + prepared queries using clsql:command-object + + * sql/{expressions,fddl, generic-postgresql, ooddl}.lisp: + Change how database identifiers are emitted to hopefully make this + less brittle, and more easily intuitable. + + Previously every code path that wanted to emit a + database identifier was responsible for coercing what was provided + into a correctly escaped string. Sometimes two or three functions + in a row were trying to correctly quote and output that string. I + have tried to centralize this type coercion and logic into a + single code path. + + everything should now call (escaped-database-identifier thing) + immediately before splicing a database identifier into string being + sent to the database + + * sql/oodml.lisp: added method choose-database-for-instance, which + allows overriding which database connections are used based on + object type. Can be used to prevent connection conflicts in + multi-threaded environments + + * sql/syntax.lisp: [foo bar] and [foo.bar] read into the same + clsql expression now (they used to be output the same, but after + the above database-identifier change, they were output separately + + * test/: Better, more tests, better type coercion in tests and + throughout (%get-int) + + [edit 2012-03-28 - RT] + * sql/sequences.lisp: Sequences were previously prefixed with + _CLSQL_SEQ_ but are now suffixed with _CLSQL_SEQ. This is likely + to break existing implementations using the default sequence names + + setting *old-sequence-names* to T, should force using the older + naming scheme + + +2011-07-16 Kevin Rosenberg + * Version 5.4.0 release + +2011-06-27 Nathan Bird + * db-odbc/: memory management improvements: leak slower + * MSSQL: TOP + DISTINCT work together + +2011-06-20 Nathan Bird + + * sql/time.lisp: Handle parsing already parsed objects. + * sql/oodml.lisp: raise exception if we generate an update with no + where clause; incorporate Ryszard Szopa's patch for functional + expressions in :order-by + * sql/expressions.lisp: (listify nil) => nil instead of (nil). + * db-odbc/: bugfixes for working with older versions of FreeTDS; + support for bigints that works on mssql and postgres + * MSSQL improvements: use top instead of limit, IDENTITY can be a + column constraint, clsql:date becomes 'smalldatetime' + +2011-06-20 Nathan Bird + + * Version 5.3.4 + * db-postgresql-socket/postgresql-socket-api.lisp: Addendum + to Otto Diesenbacher's patch that had a spurious write a 0 + byte (to terminate string) that should have been just CCL. + +2011-06-12 Kevin Rosenberg + * Version 5.3.3 + * db-postgresql-socket/postgresql-socket-api.lisp: + Patch from Otto Diesenbacher for UTF8 encoded strings + for CCL. FIXME: The best patch would be to use the + user-set encoding from the database object and use + UFFI's encoding strings to/from octet vectors rather + than SB-UNICODE and CCL specific code in this file. + +2011-04-21 Kevin Rosenberg + * sql/generics.lisp: Add defgeneric for new + database-last-auto-increment-id + +2011-04-01 Kevin Rosenberg + * Version 5.3.2 + * db-mysql/mysql-client-info.lisp: Add recognition of + version 6 of MySQL client library. + * sql/metaclass.lisp: Fix the fix in the reader conditional + +2011-03-30 Kevin Rosenberg + * Version 5.3.1 + * sql/metaclasses.lisp: Fix previous patch to work + on non-SBCL systems + +2011-03-29 Kevin Rosenberg + * Version 5.3.0 + * sql/metaclasses.lisp: Apply one-line patch to fix + for newer SBCL (thanks to Nikodemus Siivola) + * many_files: Applied multiple patches from Holger Schauer + to improve autoincrement capability. + +2010-10-24 Kevin Rosenberg + * Version 5.2.0 + * db-odbc/odbc-api.lisp: Change from SBCL-specific + to UFFI version of octets-to-strings. Reported by + Daniel Brunner + * sql/oodml.lisp: Apply patch from Rupert Swarbrick + : Fix behaviour with auto-inc + primary keys. + * sql/expressions.lisp, tests/test-syntax.lisp: Apply + patch from Russ Tyndall to quote identifiers with space + or special character. + +2010-09-20 Kevin Rosenberg + * Version 5.1.4 + * sql/{pool,database}.lisp: Pass encoding argument to + connections made from pool and with reconnect. + +2010-08-16 Kevin Rosenberg + * Version 5.1.3 + * db-odbc/odbc-{api,dbi}.lisp: Commit patch from + Memet Bilgin to fix issue with unicode and ODBC. + +2010-08-16 Kevin Rosenberg + * Version 5.1.2 + * uffi/clsql-uffi.lisp: Commit patch from JT Klein fixing + invocation of uffi:convert-from-foreign-string macro. When + time allows, I'll investigate changing UFFI's macro to + a function call and then revert this patch. + +2010-06-15 Kevin Rosenberg + * Version 5.1.1 + * clsql-{uffi,mysql}.asd: Modify operation-done-p functions + to guard against change introduced in new ASDF traversing. + +2010-04-20 Kevin Rosenberg + * Version 5.1.0 [DEPENDENCY UPGRADE: UFFI 2.x needed] + * clsql-uffi.asd: Depend on UFFI version >= 2.0 + to support foreign encoding of strings. + +2010-04-16 Kevin Rosenberg + * Version 5.0.6 + * db-postgresql.lisp, sql/fddl.lisp: Fix typos [Thanks to + Walter C. Pelissero] + * sql/metaclasses.lisp: Work around type-check-function being set + during defclass expansion in SBCL [Thanks to Walter C. Pelissero] + * uffi/clsql-uffi.lisp: In call to uffi:convert-from-foreign-string, + Set null-terminated-p to T when length not specified. + [Thanks to Walter C. Pelissero] + +2010-03-21 Kevin Rosenberg + * Version 5.0.5 + * sql/fdml.lisp: Fix DO-QUERY to actually return the last value of + the body. + +2010-03-02 Nathan Bird + * doc/: Added a README on how to build doc; now builds on Ubuntu. + * sql/oodml.lisp: READ-SQL-VALUE now has explicit method for + handling double-floats and the default method will no longer + attempt to convert values that have already been converted. + * sql/syntax.lisp: Introduce file-enable-sql-reader-syntax which + enables the syntax for the scope of the file without trying to + keep track of the current syntax state. + * sql/pool.lisp: Introduce + clsql-sys:*db-pool-max-free-connections* which is a heuristic + threshold for when to disconnect a connection rather than + returning it to the pool. + * sql/pool.lisp: Check connections for validity before returning + to the user. + +2010-03-01 Kevin Rosenberg + * db-mysql/mysql-api.lisp: Remove spurious enumeration + +2010-02-16 Kevin Rosenberg + * Version 5.0.4 + * db-mysql/mysql-api.lisp: Fix mysql_options UFFI parameter list + * doc/ref-connect.xml: Document the MySQL options parameter as + part of the connection-spec. + +2010-02-15 Kevin Rosenberg + * db-mysql/mysql-{api,sql}.lisp: Support sending options + to MySQL using mysql_options, which occurs between the API calls + of mysql_init and mysql_real_connect. + +2010-02-11 Kevin Rosenberg + * Version 5.0.3 + * multiple-files: Further internationalization. Change + UFFI:CONVERT-RAW-FIELD and UFFI:CONVERT-FROM-FOREIGN-STRINGS + invocations to use the foreign character set encoding of the + database object. Requires UFFI v.1.8.6 + * Makefile.common: Fix OS_DARWIN64 setting + +2010-02-11 Nathan Bird + * MSSQL: better support for fddl 'date type. + +2010-02-11 Kevin Rosenberg + * Makefile.common, uffi/Makefile, db-mysql/Makefile: + Better support OS X Snow Leopard by building universal + (x86_64,i386) dylib bundles + +2010-02-08 Kevin Rosenberg + * Version 5.0.2 + * sql/database.lisp: Fix missing slot-accessor + (Thanks to Stelian Ionescu) + * sql/generics.lisp: Add missing keyword to defgeneric + (Thanks to Stelian Ionescu) + +2010-02-07 Kevin Rosenberg + * Version 5.0.1 + * sql/{base-classes,database}.lisp: Add encoding slot for + non-ASCII strings. + * db-mysql/mysql-sql.lisp: Use UFFI:FOREIGN-ENCODED-OCTET-COUNT. + Requires UFFI version 1.8.2 or above. + +2010-02-06 Kevin Rosenberg + * Version 5.0.0: First release of CLSQL to formally and + consistently support non-ASCII strings with encoding of external + formats for SQL strings. UFFI version 1.8.1 is higher is required. + + This change may introduce some differences in string handling for + people who are using non-ASCII encoded characters. Thus, because + of the risk of BACKWARD INCOMPATIBILITY, the major version number + was incremented with this release. + +2010-02-06 Kevin Rosenberg + * tests/test-i18n.lisp: Bind UFFI:*DEFAULT-EXTERNAL-FORMAT* + for testing multibyte encodings. + * uffi/clsql-uffi.lisp: Changes for UFFI 1.7.4's new support + for encoding foreign strings with a specified external format. + +2010-02-06 Kevin Rosenberg + * sql/metaclasses.lisp: If no declared slot type in + compute-lisp-type-from-specified-type, then use t as lisp type. + Issue noted when testing Clozure CL 1.4. + +2010-02-06 Kevin Rosenberg + * tests/test-init.lisp: Turn off the benign console notices for + testing on postgres. + +2010-02-05 Kevin Rosenberg + * clsql-test.asd, tests/{test-i18n,test-init}.lisp: + Load test-i18n.lisp and use its tests as long as 'uffi:no-i18n is + not present in cl:*features*. This requires UFFI 1.7.2 or above. + +2010-02-05 Kevin Rosenberg + * sql/utils.lisp: Reading #\no-break_space causes an + error for non-unicode SBCL. Conditionalize read of + #\no-break_space for non-unicode SBCL and 8-bit string + Allegro. + +2010-02-03 Kevin Rosenberg + * tests/test-init.lisp: Add *test-report-width* variable + and word-wrap skipped test reason field. + +2010-01-29 Kevin Rosenberg + * Version 4.3.3 + * clsql-cffi.asd: New file that causes CLSQL to use + CFFI-UFFI-COMPAT library rather than UFFI. Perform 'asdf:load-op + on CLSQL-CFFI rather than CLSQL system to use CFFI-UFFI-COMPAT. + +2010-01-29 Nathan Bird + * tests/*.lisp: A lot more tests and test setup tweaks. + * sql/expressions.lisp: output-sql on sql-relational-exp does + better arity checking now. (apply #'sql-and some-list) gives + better results. + +29 Jan 2009 Kevin Rosenberg + * sql/oodml.lisp: Fix for UPDATE-RECORD-FROM-SLOTS for normalized + view classes + +28 Jan 2009 Kevin Rosenberg + * Version 4.3.2 + * Change "normalise" from British spelling for consistency with + other American spellings in CLSQL. + +28 Jan 2009 Kevin Rosenberg + * db-mysql/Makefile: Add directory for Fedora 11/12 on 64-bit + platform (Thanks to Michael Pheasant) and remove a 32-bit directory + +28 Jan 2009 Kevin Rosenberg + * Version 4.3.1 + * sql/utils.lisp: Ensure Lispworks 6 lock is created in sharing mode + +20 Jan 2009 Nathan Bird + * Version 4.3.0 + * Rewrite tests to use datasets + +07 Jan 2009 Kevin Rosenberg + * sql/utils.lisp: Changes to support Lispworks 6 + +10 Dec 2009 Kevin Rosenberg + * Version 4.2.0 + * doc/ref-ooddl.lisp: Add needed CDATA escapes + * doc/clsql.pdf, doc/html.tar.gz: Build new manuals with + normalized view classes. + +10 Dec 2009 Kevin Rosenberg + Large patch from Thijs Oppermann to add + support for normalized view classes. When having view class that + inherit from others, CLSQL by default builds tab all the columns + from the parent in the child. This patch is meant to normali so + that a join is done on the primary keys of the concerned tables to + get a set. + +10 Dec 2009 Kevin Rosenberg + * sql/time.lisp: Patch from Oleg Tihonov to SYNTAX-PARSE-ISO-8601 + to properly parse fractions of seconds. + +10 Dec 2009 Kevin Rosenberg + * sql/time.lisp: Patch from Oleg Tihonov to roll function + to properly use USEC argument. + +21 Nov 2009 Kevin Rosenberg + * Version 4.1.2 + * Makefiles: On 64-bit Linux systems, try to build both 32 and 64-bit + interface libraries. This requires the installation of multiarch build tools + as well as 32-bit support libraries (libc, libz and libmysqlclient). + +04 Sep 2009 Kevin Rosenberg + * Version 4.1.1 + * sql/fdml.lisp: Rework do-query to use supplied database + parameter when passed a sql-object-query + (thanks to JTK ) + * sql/generic-postgresql.lisp: Allow optional connect + parameters for postgresql databases (thanks to Stephen Compall) + * doc/ref-{clsql,connect}.xml, sql/db-interfaces.lisp: + Fix transposed letters (thanks to Stephen Compall) + * db-mysql/Makefile: Add directory for MacPorts mysql5 port + (thanks to Stephen Compall) + * sql/database.lisp: Have database-type default be + *default-database-type* (thanks to Desmond O. Chang) + * sql/transactions.lisp: Improved handlining of nested + transactions (thanks to Eli Naeher) + * sql/time.lisp: Commit patch to fix parse-iso-8601-duration + (thanks to Stephen Compall) + * sql/database.lisp: Use :verbose nil for asdf:operate + invocation (Thanks to Mackram Raydan) + * sql/metaclasses.lisp: Rework initialize-instance for + view-class-direct-slot-definition (thanks to Stephen Compall) + +31 Aug 2009 Kevin Rosenberg + * sql/db-interface.lisp: Fix spelling error (thanks to + David Thompson) + * clsql-mysql.asd/db-mysql/mysql-loader.lisp: Commit + patch from Andreas Franke to honor windows drive letter + when loading clsql_mysql C library. + +31 Aug 2009 Kevin Rosenberg + * Version 4.1.0 + * db-mysql/mysql-{sql,api}.lisp,db-mysql/clsql_mysql.c: + Latest version of mysqlclient once again changed the C + structures from which the mysql backend reads data. + Rather than adding yet another structure definition which + is enabled by the client version as read by mysql-client-info.lisp, + the mysql backend has been re-written to ignore in the internal + C structures. Instead, C wrapper functions for slot access have + been added to clsql_mysql.c and are used by mysql-sql.lisp to + access the internals of C structures. This adds a bit of overhead, + but completely separates the lisp code from trying to keep up + with the continually changing internal C structure of mysql. + All tests in the test suite executed correctly with this change. + +31 Aug 2009 Kevin Rosenberg + * Version 4.0.6 + * sql/database.lisp: Fix syntax on process-lock (thanks to + Stian Sletner) + * db-mysql/mysql-sql.lisp: Allow use of database parameter + in database-list function (thanks to Michael Pheasant) + * sql/oodml.lisp: Allow NULL values for floating-point fields + (thanks to Jan Rychter) + +02 Jun 2009 Kevin Rosenberg + * Version 4.0.5 + * sql/database.lisp: Add process-lock for deleting database from + a pool (thanks to Ralf Mattes). + +25 Feb 2008 Kevin Rosenberg + * Version 4.0.4 + * sql/expressions.lisp: Remove stray form (thanks to Samuel Ward) + +12 Dec 2007 Kevin Rosenberg + * sql/expressions.lisp: Bind *in-subselect* when outputting + selections (patch from unknown source). + +11 Dec 2007 Kevin Rosenberg + * Version 4.0.3 + * sql/metaclasses.lisp: Unify base-table processing by extracting + the correct code from initialize-instance :around into the helper + function set-view-table-slot. Call that function also in + reinitialize-instance :around replacing erroneous code discovered + by Josh Feinstein. + +17 Nov 2007 Kevin Rosenberg + * BUGS: Add note about benefit of using configure file to create + Makefiles (suggestion from Joe Corneli) + +22 Oct 2007 Kevin Rosenberg + * Version 4.0.2 + * db-postgresql/postgresql-sql.lisp: Patch from Gabriele Favalessa based on + Andrew Golding suggestion for more informative :error-id slot. + +17 Sep 2007 Kevin Rosenberg + * Version 4.0.1 + * db-mysql/mysql-sql.lisp: Convert query to uffi cstring (thanks to + Albert Krewinkel). + * doc/Makefile, doc/html.xsl: Change output encoding from ISO-8859-1 to UTF-8 + +14 Sep 2007 Kevin Rosenberg + * Version 4.0.0: Major version increase to warn of potential + backwards incompatibility. + * NEWS: Document potentional backward incompatible changes + * db-mysql/mysql-sql.lisp: Changes session SQL mode to ANSI immediately + after connecting. This may break compatibility with some applications + who are using non-ANSI features with MySQL. This change is required to + properly support view-classes using a string as their :base-table + attribute. This allows users to specify the case of table names. + This is feature is even more essential for MySQL itself since MySQL + uses case-sensitive table names. Use connection-based database-create + and database-destroy rather than trying to invoke command-line mysql + utility. Remove automatic upcasing of strings from list-indices. + * db-postgresql/postgresql-sql.lisp: Use connection-based + database-create and database-destroy rather than trying to invoke + command-line utilities. + * db-postgresql-socket/postgresql-socket-sql.lisp: Use + database-execute-command rather than execute-command for + database-{create,destroy}. Connect to postgres database + rather than template1 for those database creation/deletion. + * sql/metaclasses.lisp: Store the string value of :base-table if a + string is provided. Perform sql-escape at time of view-table name + creation. + * tests/test-init.lisp: Use "ej_join" as a string, rather than a + symbol, since "ej_join" is specified as :base-table. Clear the + expression output-cache in case the code for generating sql output + has changed. + * test/test-oodml.lisp: whitespace fix + * sql/ooddl.lisp: Use quoted string for primary key constraint if + table name is specified as a string. + * sql/oodml.lisp: Don't convert a string view-table name to database's + default case. + * sql/expressions.lisp: Properly handle table and attribute identifiers + when they are a string. Do not change case of symbols to match database + default case. + * sql/operations.lisp: Change multiword symbols to upper case. + * sql/fddl.lisp: Quote base-table if a string to preserve case + for drop-table and create-table. + * tests/test-syntax.lisp: Add tests of low-level string attribute + identifiers. + +20 Jul 2007 Kevin Rosenberg + * Version 3.8.6 + * db-oracle/oracle-loader.lisp: Rework use of ORACLE_HOME directory + (problem noted on clsql-devel by icardo Boccato Alves) + * sql/pool.lisp: Remove incorrect keyword + * sql/database.lisp: Rework WITH-DATABASE to not make the database the + default database (reported by Saurabh Nanda and Chaitanya Gupta) + * doc/ref-connect.lisp: Update the documentation to WITH-DATABASE to + emphasis that make-default has a default value of nil. + * sql/transaction.lisp: Adjust commit/rollback messages for Microsoft + SQL Server. (patch from Nathan Bird) + * sql/metaclasses.lisp: Use finalize-inheritance hack on SBCL because + of trouble with def-view-class compilations (patch from Nathan Bird) + +15 Jul 2007 Kevin Rosenberg + * Version 3.8.5 + * db-mysql/mysql-loader.lisp: Revert previous change since libmysql is + not dynamically loaded on Windows + +22 Jun 2007 Kevin Rosenberg + * Version 3.8.4 + * db-mysql/mysql-loader.lisp: Do not bother loading libmysqlclient since + that library is dymically linked to clsql-mysql library. Thus, the mysql + library will be automatically loaded. This has only been tested on Linux, + thus far. + +29 May 2007 Kevin Rosenberg + * tests/test-fddl.lisp: Add :order-by for :fddl/big/1 as + reported by Ricardo Boccato Alves + +02 May 2007 Kevin Rosenberg + * sql/database.lisp: Add ability of WITH-DATABASE to return + multiple values (patch from Liam Healy) + +25 Apr 2007 Kevin Rosenberg + * Version 3.8.3 + * doc/connect.xml: variable description fix by Liam Healy + * db-sqlite3/sqlite3-api.lisp, uffi/clsql-uffi-loader.lisp: + Apply patches from Marcus Pierce to reduce load-time warnings + * sql/package.lisp: Export iso-timestring as requested by Kevin Blaisdell + +23 Mar 2007 Kevin Rosenberg + * Version 3.8.2 + * sql/db-postgresql-socket-api.lisp: Change read-socket-sequence to + disable wide characters for crypt salt sequence on SBCL, based + on patch from Lars Nostdal. + +26 Jan 2007 Kevin Rosenberg + * Version 3.8.1 + * sql/pool.lisp: Test pooled connection when popped from + the pool to ensure the connection still works. Currently, implemented + only for MySQL. + +17 Jan 2007 Kevin Rosenberg + * db-mysql/Makefile: Add potential mysql directories + +31 Dec 2006 Kevin Rosenberg + * sql/metaclasses.lisp: Remove usused saved-initargs + * clsql.asd: Also check ~/.clsql-init.lisp at load-time (usually used to + push search libraries) + +30 Dec 2006 Kevin Rosenberg + * Version 3.8.0: BACKWARD INCOMPATABLE CHANGE! + * db-postgresql/postgresql-{api,loader,sql,package}.lisp: + Apply patch from Edi Weitz to avoid conflict with new Lispworks 5 + POSTGRESQL package name. CLSQL's new package will be PGSQL, however + on non-Lispworks platforms, the nickname POSTGRESQL will still be + available. Applications directly using low-level POSTGRESQL package + are recommended to use the new PGSQL name. + * db-oracle/oracle-{api,sql}.lisp, sql/{expressions,loop-extension}.lisp, + Apply patch from Edi Weitz to reduce compiler warnings. + +28 Dec 2006 Kevin Rosenberg + * Version 3.7.9 + * sql/expressions.lisp: Commit patch from Edi Weitz to + use *default-database* for SQL-OUTPUT if no database is + explicitly passed to function. + * uffi/clsql-uffi-loader.lisp: Change load order to first try + plain name/type before attempting user-specified paths (patch + from Edi Weitz) + * uffi/ptrbits.c: New file to return number of bits in an pointer + * uffi/Makefile: use intbits to name .so file based on bit size. + Build both 32-bit and 64-bit libraries on 64-bit platform. + +30 Nov 2006 Kevin Rosenberg + * Version 3.7.8 + * db-sqlite3/sqlite3-sql.lisp: Commit patch from Edi Weitz fixing + error display + +16 Oct 2006 Kevin Rosenberg + * Version 3.7.7 + * db-postgresql/postgresql-sql.lisp: Remove + uffi:convert-foreign-to-native wrapper since using cstring for + PQresultErrorField + +16 Oct 2006 Kevin Rosenberg + * Version 3.7.6 + * db-postgresql/postgresql-api.lisp: Fix UFFI return type for + PQresultErrorField foreign function. + +16 Oct 2006 Kevin Rosenberg + * Version 3.7.5 + * doc/intro.xml: Update supported platforms. + * db-postgresql/postgresql-{package,api,sql}.lisp: Apply + changes from Andew Golding to use a more-specific error code + from PostgreSQL than the generic fatal error code of the result set. + +03 Oct 2006 Kevin Rosenberg + * sql/syntax.lisp: Commit patch from Marcus Pearce to improve + readtable modifications + +02 Oct 2006 Kevin Rosenberg + * sql/syntax.lisp: Check that original reader syntax functions + stored before trying to restore them. + +20 Sep 2006 Kevin Rosenberg + * sql/syntax.lisp: Apply patch from Marcus Pearce to correctly + display sql reader syntax. + +06 Sep 2006 Kevin Rosenberg + * uffi/clsql-uffi-loader.lisp, db-mysql/mysql-loader.lisp: Change from using *features* + to decide on 64-bit platform and check size of most-positive-fixnum instead. + Needed to support clisp amd64. + +03 Sep 2006 Kevin Rosenberg + * Version 3.7.1 + * sql/metaclasses.lisp: Rework slot type's to be more AMOP + compatibile. Add warning for a metaclass condition that should + not occur. + * sql/time.lisp: Fixed symbol case inconsistency causing problem + in AllegroCL's modern lisp. First sign of bug noted by + Joel Reymond on clsql-devel. + * clsql.asd: Make time.lisp depend on utils.lisp + +31 Aug 2006 Kevin Rosenberg + * db-mysql/mysql-loader.lisp: Apply patch from Marcus Pearce to push + *library-file-dir* to CLSQL's library path. + +30 Aug 2006 Kevin Rosenberg + * Version 3.7.0: New platforms supported: SBCL/Win32, CLISP/Win32, CLISP/Cygwin, + CLISP/Linux x86, CLISP/Linux amd64, OpenMCL amd64. CLISP support requires the + latest development versions of CLISP, cffi, and cffi-uffi-compat packages. + * Makefile.common: Add OS detection + * uffi/make.sh, db-mysql/make.sh: Remove files + * uffi/Makefile, db-mysql/Makefile: Add support for cygwin compilation. + Refactor to remove need to make.sh shell scripts. + * clsql.asd: Add support for loop extensions for clisp. Support clisp via cffi. + * sql/loop-extension.lisp: Define loop-record-iteration-path in CLSQL-SYS + package rather than CL-USER. Add support for ansi-loop on clisp. + * sql/ansi-loop.lisp: New file to support iteration on clisp. + * db-mysql/mysql-api.lisp: Remove old mysql C API functions that no + longer exist in the mysql client library. + * doc/ref-fdml.lisp: Correct default field-type. + * sql/expressions.lisp: Use database-output-as-type if value exists for boolean output. + Fixed bug with noted with MySQL 5.0.24 and boolean values. + +28 Aug 2006 Kevin Rosenberg + * Version 3.6.7 + * sql/oodml.lisp: Remove high debugging level declaration + +14 Aug 2006 Kevin Rosenberg + * Version 3.6.6 + * sql/generic-postgresql.lisp: Fix assumption that postgres user id + is always 1. Fixes problem noted with PostgreSQL 8.1. + +12 Aug 2006 Kevin Rosenberg + * Version 3.6.5 + * sql/generic-postgresql.lisp: Add slot for has-table-pg_roles to + lazily cache if pg_roles tables exist. Selectively use SQL from + Joel's previous patch if pg_roles table exists. Should now work + with both postgresql 7.4 and 8.x. + +12 Aug 2006 Kevin Rosenberg + * Version 3.6.4 + * clsql.asd: Add support for c:\etc\clsql-init.lisp as possible + local initialization file + * db-mysql/mysql-loader.lisp: Remove former method of using pathnames + as name candidates. + * db-odbc/odbc-api.lisp: Work-around Allegro/Windows FFI bug + that generates incorrect integer return type + * sql/generic-postgresql.lisp: Revert patch from Joel Reymont since + it fails on versions of postgresql that lack the pg_role table + +07 Jul 2006 Kevin Rosenberg + * Version 3.6.3 + * sql/transactions.lisp: Important typo fix from Alexey Antipov + for database-start-transaction + +04 Jul 2006 Kevin Rosenberg + * Version 3.6.2 + * db-postgresql/postgresql-sql.lisp: Apply patch from Vladimir Sekissov + to close connection when failing to connect to database. + * sql/generic-postgresql.lisp: Apply patch from Joel Reymont + to avoid dropping system views. + * sql/oodml.lisp: Apply patch from Joel Reymont to avoid listify + a nil value [patch sponsored by Flektor] + * clsql-uffi.asd, uffi/make.sh: Patch from Richard Kreuter + for netbsd compilation + +15 May 2006 Kevin Rosenberg + * doc/ref-ooddl.xml: Add documentation for :db-reader and :db-writer + slots for def-view-class macro [as reported missing by Thomas Fischbacher]. + +09 May 2006 Kevin Rosenberg + * db-postgresql-socket/postgresql-socket-api.lisp: + Apply patch from Marko Kocic adding the socket creation + function needed for CLISP. + +08 May 2006 Kevin Rosenberg + * Version: 3.6.0 (requires UFFI v1.5.11 or greater) + * db-oracle/metaclasses.lisp: Patch from James Bielman for + checking slot constraints. + * db-oracle/oracle-{api,sql}.lisp: Avoid dead pointers on loading + saved openmcl images (based on patch from James Bielman) + +06 May 2006 Kevin Rosenberg + * doc/ref-fdml.xml: Documentation patch from Marcus Pearce for limit keyword + +03 May 2006 Kevin Rosenberg + * Version 3.5.7 + * sql/time.lisp: Apply patch from Aleksandar Bakic to extended + duration parsing and unparsing to include year and month. + * clsql-uffi.asd, uffi/clsql-uffi-loader.lisp: Apply patch from Nathan Bird + improving library search on Windows platform. + * doc/ref-fdml.xml, /doc/TODO, tests/test-fdml.lisp, tests/test-init.lisp: + Apply patch from Marcus Pearce documenting and testing :limit and :offset for SELECT + +20 Mar 2006 Kevin Rosenberg + * Version 3.5.6 + * clsql-postgresql-socket.asd, + * db-postgresql-socket/postgresql-socket-package.lisp: + Use the cl-md5 package on all platforms. Based on report + from Alan Caulkins. + +09 Mar 2006 Kevin Rosenberg + * Version 3.5.5 + * uffi/make.sh, db-mysql/make.sh: Add GNU uname + +28 Feb 2006 Kevin Rosenberg + * Version 3.5.4 + * sql/metaclasses.lisp: Apply patch from Friedrich Dominicus to + fix accessor for new versions of SBCL + * db-oracle/oracle-sql.lisp: Apply patch from James Bielman + to improving parsing of time. + * db-db2/db2-constants.lisp: Change NULL_HANDLE has suggested + by Harold Lee. + * db-oracle/oracle-dbi.lisp: Add support for SQL BIT type + as noted by Russ Tyndall. + +16 Jan 2006 Kevin Rosenberg + * Version 3.5.3 + * sql/time.lisp: Commit patch from Aleksandar Bakic + to properly handle destructive flag + * db-postgresql-socket/postgresql-socket-api.lisp: Apply patch + from Steven Harris for socket files with SBCL. + * sql/pool.lisp: Apply patch from Vladimir Sekissov so that + new connections added to the pool do not become the *default-database* + * sql/connect.lisp: Optionally set *default-database* for pooled + connection when make-default is generalized true. + +23 Dec 2005 Kevin Rosenberg + * Version 3.5.1 + * sql/expressions.lisp: Ensure table names are properly escaped + before comparing -- fixes bug reported by Asbjørn Bjørnstad + on CLSQL-Devel. + +02 Dec 2005 Kevin Rosenberg + * sql/generic-postgresql.lisp: improved decoding of table attribute + parameters [from Vladimir Sekissov] + * sql/metaclasses.lisp: check that metaclass is standard-db-class or + it's subclass to prevent adding standard-db-object to supers if + somebody in the path has it already when metaclass inherited from + standard-db-class. [from Vladimir Sekissov] + +26 Nov 2005 Kevin Rosenberg + * Version 3.5.0 + * tests/test-init.lisp, tests/test-fddl.lisp, tests/test-fdml.lisp, + * db-odbc/odbc-api.lisp, db-odbc/odbc-ff-interface.lisp, + * db-odbc/odbc-package.lisp, db-odbc/odbc-constants.lisp + * db-odbc/odbc-dbi.lisp, db-odbc/odbc-sql.lisp + * sql/fddl.lisp, sql/generic-odbc.lisp, sql/db-interface.lisp + * sql/transaction.lisp, sql/package.lisp, sql/time.lisp + Commit patch from Dominic Robinson providing support for + Microsoft SQL Server + * doc/csql.lisp: Fix typo in slot name + +24 Nov 2005 Kevin Rosenberg + * Version 3.4.7 + * sql/time.lisp: Commit patch from Aleksandar Bakic for + correct handling of decode-time usec value + * clsql-mysql.asd: Commit patch from Harald Hanche-Olsen to + correct the name of the shared library file. + +16 Nov 2005 Kevin Rosenberg + * version 3.4.6 + * sql/metaclasses.lisp: Avoid calling change-class on + effective-slot-definitions on sbcl to conform to sbcl 0.9.6.38 + changes. + +15 Nov 2005 Kevin Rosenberg + * Version 3.4.5 + * sql/expressions.lisp: Patch from James Biel to add subselects + +14 Nov 2005 Kevin Rosenberg + * Version 3.4.4 [ Requires UFFI 1.5.7+] + * db-oracle/oracle-{api,sql}.lisp: Patch from James Biel + to fix lifetime of foreign strings for Oracle calls + +13 Nov 2005 Kevin Rosenberg + * Version 3.4.3 + * db-oracle/oracle-{api,sql}.lisp: Patch from James Biel + to improve performance + +12 Nov 2005 Kevin Rosenberg + * Version 3.4.2 + * clsql-uffi.asd: Patch from James Biel improving loading + * db-oracle/oracle-{api,sql}.lisp: Patch from James Biel + to support 64-bit lisps + +12 Nov 2005 Kevin Rosenberg + * Version 3.4.1 + * sql/expressions.lisp: Escape numbers to SQL strings + at expression level. + +11 Nov 2005 Kevin Rosenberg + * Version 3.4: Add MySQL 5 support + * db-mysql/mysql-client-info.lisp: Recognize MySQL 5 + * db-mysql/mysql-sql.lisp: Add support for views in MySQL 5 + * doc/mysql-macosx-notes.txt: New document from Martin Brooks + +7 Nov 2005 Kevin Rosenberg + * src/time.lisp: Apply patch from Aleksandar Bakic for ROLL + function. + * BUGS: Added report for update-object-joins by Aleksandar Bakic + +4 Nov 2005 Kevin Rosenberg + * Version 3.3.4 [UFFI >= 1.5.5 needed] + * db-odbc/odbc-api.lisp: Apply patch from Yaroslav Kavenchuk + to add missing #\' character. + * clsql.asd: Add support for CLSQLINIT environmental variable, + based on patch from Yaroslav Kavenchcuk. New version of UFFI + required. + +30 Oct 2005 Kevin Rosenberg + * Version 3.3.3 + * sql/oodml.lisp: Apply patch from Drew Crampsie to fix + update-objects-joins when using the :target-slot attribute + +26 Oct 2005 Kevin Rosenberg + * Version 3.3.2 + * sql/expressions.lisp: Avoid parenthesis on multiple group-by fields + as noted by Harald Hanche-Olsen. + * tests/test-syntax.lisp: Add test for multiple field group-by + +25 Oct 2005 Kevin Rosenberg + * Version 3.3.1 + * sql/time.lisp: Commit patch from Alan Shields to + display escape string on wall-time display only when *print-escape* + is true. + +11 Oct 2005 Kevin Rosenberg + * sql/metaclasses.lisp: Commit patch from Will to + properly set db-reader slot in effective-slot-definition + * sql/expressions.lisp: Commit patch from Alan Shields + adding make-load-form for sql-relational-exp + * sql/generic-postgresql.lisp: Commit patch from Aleksandar Bakic + adding support for new NUMBER type + +17 Sep 2005 Kevin Rosenberg + * Version 3.3.0 + * sql/time.lisp: Apply patch from Alan Shields adding DATE type. + * doc/ref-ooddl.xml: Documentation of new type + * notes/add-type-hints.txt: New file from Alan Shields + * sql/fddl.lisp: Add owner keyword to drop-table as suggested + by Francis Leboutte + * db-postgresql-socket/postgresql-socket-sql.lisp: Fix database-probe + as noted by Francis Leboutte. Similar fix applied to db-mysql and + db-postgresql. + * sql/expressions.lisp: Allow string table names for output as + contributed by Francis Leboutte. + * examples/clsql-tutorial.lisp: Support :postgresql-socket as noted + by Francis Leboutte + +08 Sep 2005 Kevin Rosenberg + * Version 3.2.4 + * doc/into.xml: Change download from ftp to http protocol + +08 Sep 2005 Kevin Rosenberg + * Version 3.2.3 + * db-oracle/oracle-sql.lisp: Correction for v3.2.2 changes by + Edi Weitz + +08 Sep 2005 Kevin Rosenberg + * Version 3.2.2 + * db-oracle/oracle-sql.lisp: Add check for zero increment as + suggested by Edi Weitz. Add missing database-sequence-last function + as noted and tested by Edi Weitz. Ensure that UFFI buffer is freed + in handle-oci-error. Add unwind-protect to sql-stmt-exec. Free UFFI + stmthp object when query cursor is freed with OCI. + +22 Aug 2005 Kevin Rosenberg + * uffi/clsql-uffi-loader.lisp: Commit patch from astor@pvv.ntnu.no to + display search path when error occurs loading foreign library. + +05 Jul 2005 Kevin Rosenberg + * Version 3.2.1 + * doc/appendix.xml: Incorporate Edi Weitz's notes into documentation + with changes to support case-sensitive lisp implementations. Add + paragraph on using /etc/clsql-init.lisp site initialization file. + * clsql.asd: Load file /etc/clsql-init.lisp, if it + exists, after package is loaded to set site-specific configuration + +2005-06-24 Edi Weitz + * sql/db-interface.lisp: Added new special variable + *FOREIGN-LIBRARY-SEARCH-PATHS* and function PUSH-LIBRARY-PATH to + manipulate it. + * sql/package.lisp: Export these. + * uffi/clsql-uffi-loader.lisp: Used new variable; changed order of + libs. + * db-mysql/mysql-loader.lisp: Changed order of libs. + +09 Jun 2005 Kevin Rosenberg + * Version 3.2.0: REQUIRES UFFI VERSION 1.4.38 OR HIGHER + * clsql-mysql.asd: Renamed clsql/mysql interface library from + mysql to clsql_mysql + * clsql-uffi.asd: Renamed clsql/uffi interface library from + mysql to clsql_uffi + * uffi/clsql_uffi.c: Renamed from uffi.c + * db-mysql/clsql_mysql.c: Renamed from mysql.c + * db-mysql/Makefile, db-mysql/Makefile.msvc, db-mysql/mysql-loader.lisp: Rename shared library + * uffi/Makefile, uffi/Makefile.msvc, uffi/uffi-loader.lisp: Rename shared library + * db-*/*-loader.lisp: Commit big patch from Edi Weitz to remove + absolute pathnames when searching for foreign libraries. + foreign library loading. + +07 Jun 2005 Kevin Rosenberg + * Version 3.1.16 + * db-mysql/mysql-api.lisp: Commit patch from Espen Wiborn + to support UTF-8 on sbcl unicode. + +18 May 2005 Kevin Rosenberg + * Version 3.1.15 + * sql/time.lisp: Fix bug in roll that caused failure in test suite + +17 May 2005 Kevin Rosenberg + * Version 3.1.14 + * sql/oodml.lisp: Properly handle when db-writer is NIL + +11 May 2005 Kevin Rosenberg + * sql/expressions.lisp: Avoid using simple-string declaration when + a non-simple string may be encountered. [issue noted by + will@cesmail.net] Add a simple-string declaration for a local + string generated. + +06 May 2005 Kevin Rosenberg + * sql/oodml.lisp: Change db-writer and db-reader processing to accept + a symbol as well as function to serve as a function designator + [issue noted by will@cesmail.net] + +05 May 2005 Kevin Rosenberg + * Version 3.1.13 + * sql/time.lisp: Fix error in submitted patch which caused error + in timestrings with 19 or less characters. + +27 Apr 2005 Kevin Rosenberg + * Version 3.1.12 + * db-postgresql-socket/postgresql-api.lisp: Commit patch from Tim Howe + to fix read-socket-sequence on non-sb-unicode sbcl. + +26 Apr 2005 Kevin Rosenberg + * Version 3.1.12 + * sql/time.lisp: Commit patch from Daniel Lowe which adds support + for fractional seconds which is required by PostgreSQL + * db-postgresql/postgresql-loader.lisp: Add library path for Windows + +25 Apr 2005 Kevin Rosenberg + * sql/csql.lisp: Update URL for "SQL for Web Dummies" [Sean Champ] + +24 Apr 2005 Kevin Rosenberg + * Version 3.1.11 + * sql/syntax: Commit patch from Alan Shields to supress reader + macros when *read-supress* is T. + +13 Apr 2005 Kevin Rosenberg + * Version 3.1.10 + * db-postgresql-socket/postgresql-socket-api.lisp: Commit patch + from Janis Dzerins to support unicode on SBCL + * sql/syntax: Commit patch from Alan Shields to improve reporting + of invalid syntax statements. + +06 Apr 2005 Kevin Rosenberg + * Version 3.1.9 + * db-mysql/mysql-sql.lisp: Add port to connection specification + based on patch from Dave Watson + * doc/appendix.xml: Document MySQL port parameter to connection spec + +03 Apr 2005 Kevin Rosenberg + * Version 3.1.8 + * sql/time.lisp: Patch from Keith James for parsing ISO-8601 timestamps + +18 Mar 2005 Kevin Rosenberg + * sql/oodml.lisp: Add missing database argument [Patch from + Alan Caulkins] + +03 Mar 2005 Kevin Rosenberg + * sql/oodml.lisp: Improve database priority in + update-records-from-instance [Patch from Walter C. Pelissero] + +17 Feb 2005 Kevin Rosenberg + * Version 3.1.7 + * sql/package.lisp: Export database-reconnect from clsql-sys + +08 Feb 2005 Kevin Rosenberg + * sql/oodml.lisp: Use explicit database in fault-join-target-slot + [Patch from Walter Pelissero] + +29 Jan 2005 Kevin Rosenberg + * db-postgresql/postgresql-loader.lisp: Add additional + directories to Fink on darwin [patch from Cyrus Harmon]. + +29 Jan 2005 Kevin Rosenberg + * Version 3.1.6 + * sql/oodml.lisp: Clear view instance cache when delete-instance-records + is invoked [suggested by Alan Shutko]. + * uffi/clsql-uffi-loader.lisp: Improvements for loading with SBCL X86-64 + * sql/metaclasses.lisp: Don't change case of a :base-table string supplied + to def-view-class [fix suggested by Fred Gilham]. + +25 Jan 2005 Kevin Rosenberg + * tests/*.lisp: Change Vladamir to Vladimir per Cyrus Harmon's + suggestion. + * sql/utils.lisp: Fix unnecessary consing noted by Fred Gilham. + * doc/*.xml: Fix spelling of Vladimir + +24 Jan 2005 Kevin Rosenberg + * doc/bookinfo.xml, doc/csql.xml, doc/intro.xml: Update links + now that LispWorks is an independant company [noted by + Martin Thornquist] + +22 Jan 2005 Kevin Rosenberg + * db-sqlite/sqlite-sql.lisp, db-sqlite3/sqlite3-sql.lisp: + Better support for 64 bit environments + +05 Jan 2005 Kevin Rosenberg + * Version 3.1.5 + * sql/metaclass.lisp: Make t the default value of :set + [noted by Cyrus Harmon] + +28 Dec 2004 Kevin Rosenberg + * Version 3.1.4 + * uffi/clsql-uffi.lisp: Add support for unsigned integers + * db-mysql/mysql-sql.lisp: Add support for detecting/marking + unsigned integers. Apply patch from Yannick Gingras to + implement database-sequence-last. + +26 Dec 2004 Kevin Rosenberg + * doc/ref-fdml.lisp: Fix variable tag name to varname + [noted by Eduardo Munoz] + * db-mysql/mysql-loader.lisp: Handle library paths for 64-bit systems + +06 Dec 2004 Kevin Rosenberg + * Version 3.1.2 + * sql/ooddl.lisp: Accept patch from Klaus Harbo for + update-object-joins. + * sql/metaclass.lisp: Remove unnecssary (and runtime error + causing) change-class invocation when running on CLISP. + * db-mysql/mysql-api.lisp: Commit patch from Paul Werkowski + to fix structure name. + * sql/database.lisp: More specific error message with trying + to use a database value of NIL. + * sql/expressions.lisp: Accept a string for the table name + in (sql-output sql-delete database) [suggested by Ed Symanzik]. + +11 Nov 2004 Kevin Rosenberg + * Version 3.1.1 + * sql/generic-postgresql.lisp: Commit patch from Chris Capel to + ignore columns which have been dropped. + * clsql-postgresql-socket.asd, db-postgresql-socket/postgresql-socket-package.lisp: + Use sb-md5 package on SBCL, recommended by Chris Capel + +09 Nov 2004 Kevin Rosenberg + * Version 3.1.0 released: New SQLite3 backend by Aurelio Bignoli + * doc/appendix.xml: Document SQLITE3 backend, patch by Aurelio Bignoli + * sql/operations.lisp: Add lower and upper SQL functions [Daniel Lowe]. + +08 Nov 2004 Kevin Rosenberg + * sql/expressions.lisp: Fix slot name [thanks to Daniel Lowe] + +31 Oct 2004 Kevin Rosenberg + * clsql-sqlite3, db-sqlite3/*: NEW BACKEND + contributed by Aurelio Bignoli + +23 Oct 2004 Kevin Rosenberg + * sql/oodml.lisp: Commit patch from Walter Pelis + to use an object's database for a select on its slot. + +20 Oct 2004 Kevin Rosenberg + * uffi/uffi.c, uffi/clsql-uffi.lisp: Commit patch from + Aurelio Bignoli to fix negative 64-bit integers + +07 Oct 2004 Kevin Rosenberg + * db-mysql/mysql.c: Fix parameters in bind_param call + +07 Oct 2004 Kevin Rosenberg + * uffi/clsql-uffi.lisp: Add support for :blob result-type + * db-mysql/mysql-sql.lisp: Add support for :blob + result-type + +04 Oct 2004 Kevin Rosenberg + * db-mysql/mysql-sql.lisp, db-db2/db2-sql.lisp: Add + missing quotes for types in code that is still in development + (thanks to Joerg Hoehle) + +03 Oct 2004 Kevin Rosenberg + * Version 3.0.8 released + * db-sqlite/sqlite-*.lisp: Apply patch from + Aurelio Bignoli with improvements + +01 Oct 2004 Kevin Rosenberg + * multiple: Apply patch from Joerg Hoehle with multiple + improvements. + +01 Oct 2004 Kevin Rosenberg + * Version 3.0.7 released + * sql/oodml.lisp, sql/package.lisp, db-mysql/mysql-objects.lisp: + Add support for mediumint. + * sql/metaclass.lisp: Rework CLISP MOP handling + * sql/ooddl.lisp: Work-around to have CLISP finalize standard-db-class + +28 Sep 2004 Kevin Rosenberg + * sql/metaclass.lisp: Support CLISP's attribute name + for the type field in direct class slots + +27 Sep 2004 Kevin Rosenberg + * Version 3.0.6 released + * BUGS: New file. Document suspected SIGPIPE + interaction between SBCL and libpq used in + postgresql backend. + * doc/ref-fdml.lisp: Document the :caching and :refresh + keywords of the SELECT function. + * doc/ref-ooddml.lisp: Document the new *default-caching* + variable. + * sql/package.lisp: Export *default-caching* + * sql/oodml.lisp: Use *default-caching* to + control default caching behavior. + +21 Sep 2004 Kevin Rosenberg + * Version 3.0.5 release + * doc/appendix.xml: Add note about loading Oracle8 version + * db-oracle/oracle-loader.lisp: Support Oracle8 based on + data from David Young. + +10 Sep 2004 Kevin Rosenberg + * doc/Makefile, doc/catalog-darwin.xml: Apply + patch from Cyrus Harmon for building docs on Mac OS X + * sql/package.lisp: Add new (pre-release) clisp MOP package + +09 Sep 2004 Kevin Rosenberg + * Version 3.0.4 Release + * multiple: Remove superfluous quotes in UFFI def-type + and def-foreign-type forms. + +07 Sep 2004 Kevin Rosenberg + * Version 3.0.3 Release + * db-postgresql-socket/postgresql-socket-api.lisp: Commit patch + from Tagore Smith to call force-output after sending authentication + * db-odbc/odbc-api.lisp: Move ODBC v3 conversons + * db-odbc/odbc-sql.lisp: Load mysql or postgresql package when connecting + to a database of that type so that functions that indicate capabilities of + database are available. + +02 Sep 2004 Kevin Rosenberg + * db-odbc/odbc-api.lisp: More conversions to ODBC v3 + +02 Sep 2004 Kevin Rosenberg + * Version 3.0.2 Release + * TODO: Add note about ODBC on Windows + * db-odbc/odbc-loader.lisp: Add /winnt/system32/ to + search directories + * db-odbc/odbc-ff-interface.lisp: Change the return type + of SQLSetEnvAttr to :short + +02 Sep 2004 Marcus Pearce + * examples/clsql-tutorial.lisp: added missing initarg for the COMPANYID + slot of the employee View Class definition [reported by Franz Deuzer]. + +01 Sep 2004 Kevin Rosenberg + * Version 3.0.1 Release + * tests/test-init.lisp: Add second join class to employee-address + to test a class with two join slots. + * sql/oodml.lisp: Fix find-all function for a class with multiple + join slots + * TODO: Remove need to test/fix multiple join classes + +27 Aug 2004 Kevin Rosenberg + * db-mysql/Makefile, db-mysql/mysql-loader.lisp: accept patch + from Jon Buffington for file locations on Darwin. + +17 Aug 2004 Kevin Rosenberg + * sql/db-interface.lisp: Improve messages when functions + are passed a database object, but the method is not specialized + for that database type. + * sql/metaclasses.lisp: Fix inline declaration (reported by + Cyrus Harmon) + +14 Aug 2004 Kevin Rosenberg + * TODO: Add bug report about SQL generation with a table + containing two join slots. + * sql/oodml.lisp: Add optional size to VARCHAR type + +3 Aug 2004 Kevin Rosenberg + * Version 3.0.0 Release + * sql/expressions.lisp: Change declaration that + caused error on openmcl + * db-aodbc/aodbc-sql.lisp: Fix storage location + of odbc connection. Specialize database-query since + dbi's :types keyword is different than generic-odbc's + :result-types keyword + * sql/db-interface.lisp: Add warnings for methods + not specialized by a db backends + * tests/test-fddl.lisp: Fix case of symbols to support + case-sensitive Allegro + * db-oracle/oracle-sql.lisp: Rework errbuf in handle-oci-error + * tests/test-init.lisp: Note that odbc driver for postgresql + doesn't properly handle table ownership + * LATEST-TEST-RESULTS: update with version 3.0.0 + +1 Aug 2004 Marcus Pearce + * sql/expressions.lisp: conditionalise escaping of backslash in + generated SQL strings on backend. + * tests/test-fdml.lisp: test for escaping of backslash. + * sql/oodml.lisp: minor tidying in FIND-ALL. + +26 Jul 2004 Kevin Rosenberg + * NEWS: Initial 3.0 announcement draft + * README: Expand acknowledgements of incorporated projects + * CONTRIBUTORS: Further document Marcus Pearce contributions + +23 Jul 2004 Marcus Pearce + * sql/oodml.lisp: add DATABASE-OUTPUT-SQL-AS-TYPE method specialisation + to print floats with the exponent markers removed. + * tests/test-oodml.lisp: add tests for updating records with floats. + +22 Jul 2004 Marcus Pearce + * db-oracle/oracle-sql.lisp: enable :OWNER :ALL in DATABASE-LIST-* for + CommonSQL compatibility. + * tests/test-init.lisp: skip test :FDDL/TABLE/6 on Oracle since + this column constraint syntax is not supported. + * tests/test-fddl.lisp: change column indexed in test :FDDL/INDEXES/2 + from EMPLID to LAST_NAME since Oracle complains that EMPLID is already + indexed. + +17 Jul 2004 Marcus Pearce + * doc/ref-fdml.xml: document CACHE-TABLE-QUERIES. + * tests/test-fdml.xml: add test for CACHE-TABLE-QUERIES. + * doc/ref-ooddl.xml: minor changes to syntax and examples entries + for uniformity. + * doc/ref-oodml.xml: add examples for OODML. + * sql/oodml.lisp: ensure SELECT passes on its REFRESH argument + to FIND-ALL. + * sql/metaclasses.lisp: update docstrings. + * tests/test-init.lisp: change :db-constraints for emplid to + (:not-null :unique) as a temporary test for multiple column constraints + in DEF-VIEW-CLASS. + * tests/test-oodml.lisp: add tests for *DB-AUTO-SYNC* and + return type of (SETF SLOT-VALUE-USING-CLASS). + * TODO, doc/TODO: remove items done. + * README: fix typo. + +16 Jul 2004 Marcus Pearce + * sql/oodml.lisp: on Lispworks, use weak valued hash tables for + object caching. + * sql/expressions.lisp: Fix SQL generation for the symbol NIL. + +16 Jul 2004 Marcus Pearce + * sql/expressions.lisp: reactivate caching of generated SQL strings. + Move methods for DATABASE-OUTPUT-SQL, OUTPUT-SQL and SQL-HASH-KEY + here from sql/fdml.lisp. Rationalise behaviour of SQL-OUTPUT, + OUTPUT-SQL and DATABASE-OUTPUT-SQL. + * sql/fdml.lisp: remove disabled method ADD-STORAGE-CLASS. Move + methods for DATABASE-OUTPUT-SQL, OUTPUT-SQL and SQL-HASH-KEY to + sql/expressions.lisp. + * sql/ooddl.lisp: replace call to DATABASE-OUTPUT-SQL in + DATABASE-PKEY-CONSTRAINT with call to SQL-OUTPUT. + * sql/generics.lisp: add docstrings. + +15 Jul 2004 Kevin Rosenberg + * Version 2.11.16 + * db-oracle/oracle-sql.lisp: Remove OpenMCL specific + code in favor of fixing UFFI with James Bielman's patch + +14 Jul 2004 Kevin Rosenberg + * Version 2.11.15 + * db-oracle/oracle-sql.lisp: Apply patch for OpenMCL/OSX + from James Bielman + +14 Jul 2004 Marcus Pearce + * README, INSTALL: update URLs. Minor updates to descriptions. + * tests/README: remove stuff about editing contexts. + +12 Jul 2004 Kevin Rosenberg + * db-oracle/oracle-objects.lisp: Change *default-varchar-length* to + *default-string-length* + +12 Jul 2004 Marcus Pearce + * tests/test-syntax.lisp, tests/test-fdml.lisp: add tests for escaping + of single quotes. + * tests/test-fddl.lisp: add tests for column and table constraints + in CREATE-TABLE. Add test for OWNER keyword parameter to + LIST-TABLES (assuming same underlying machinery in other FDDL + functions). + * tests/test-init.lisp: restrict above test to postgresql and oracle. + +12 Jul 2004 Kevin Rosenberg + * db-sqlite/sqlite-sql.lisp: Fix condition as reported by Aurelio + Bignoli. + +11 Jul 2004 Kevin Rosenberg + * sql/oodml.lisp, sql/package.lisp, doc/ref-ooddl.lisp, db-mysql/mysql-objects.lisp: Add tinyint type + +10 Jul 2004 Kevin Rosenberg + * Version 2.11.14 + * doc/*.xml: documentation additionals and fixes so + that docbook passes xmllint. + +9 Jul 2004 Kevin Rosenberg + * Version 2.11.13 + * sql/fdml.lisp: Apply patch from Kim Minh Kaplan + to change escaping of single quotes. Mild optimizations + for escaped string output. + * doc/ref-ooddl.lisp: documentation additions + * doc/ref-oodml.lisp: Add purpose to functions + * TODO: Add need to test single quote escaping + +7 Jul 2004 Kevin Rosenberg + * doc/ref-ooddl.xml, doc-ref-oodml.xml: documentation additions + * sql/ooddl.lisp: Added SMALLINT type + * sql/generic-postgresql.lisp: Added INT2 as SMALLINT type + * db-mysql/mysql-objects.lisp: Added SMALLINT type + * sql/package.lisp: Export SMALLINT + * sql/expressions.lisp: Add MYSQL's UNSIGNED and ZEROFILL as + db-constraints + +6 Jul 2004 Marcus Pearce + * sql/expressions.lisp: add AUTO-INCREMENT and UNIQUE to the + recognised column constraints for CREATE-TABLE and the :DB-CONSTRAINTS + View Class slot option. + * sql/ooddl.lisp: fix bug preventing the :DB-CONSTRAINTS View Class + slot option accepting a list of constraints [reported by Travis Cross]. + * doc/ref-fddl.xml: add some examples of specifying column and + table constraints to the documentation for CREATE-TABLE. + * TODO: add note about adding tests for table/column constraints. Add + optimisation note about using cached attribute types in insert/update + operations. + +3 Jul 2004 Marcus Pearce + * doc/appendix.xml: add notes about idiosyncrasies/unsupported + features and so on the documentation for each backend. + * doc/TODO: remove items done. + * doc/ref-transaction.xml: add note in introduction about + autocommit mode as a difference from CommonSQL. + * doc/ref-syntax.xml: add notes about SQL syntax state functions + being macros in CLSQL. Add note about SQL operators which are + CLSQL extensions. + +2 Jul 2004 Marcus Pearce + * Version 2.11.12 released + * doc/ref-recording.xml: document SQL I/O recording. + * doc/ref-prepared.xml: new file for documenting prepared statements. + * doc/clsql.xml: comment out include for large object and CLSQL-SYS + documentation. + * doc/ref-conditions.xml: complete documentation of condition system. + * doc/global-index.xml: add symbols from condition system and remove + those from LOB and prepared statement APIs. + * doc/ref-fdml.xml: complete documentation of FDML. + * doc/glossary.xml: add View Class. + * doc/TODO: remove items done. + * sql/conditions.lisp: make SQL-CONDITION a parent of SQL-ERROR. + * sql/package.lisp: remove FOR-EACH-ROW from exports list. Export + additional slot accessors for condition classes. + +1 Jul 2004 Kevin Rosenberg + * doc/ref-ooddl.lisp: Add documentation + * doc/ooddl.lisp: Move *DEFAULT-VARCHAR-LENGTH* from oodml.lisp + and rename to *DEFAULT-STRING-LENGTH*. Add docstring. + * doc/oodml.lisp: Rename references to new name of + *DEFAULT-STRING-LENGTH* + +1 Jul 2004 Marcus Pearce + * doc/ref-transaction.xml: document transaction handling. + * sql/transaction.lisp: ensure that COMMIT, ROLLBACK and + START-TRANSACTION return NIL as per the CommonSQL spec. Modify + ADD-TRANSACTION-{ROLLBACK|COMMIT}-HOOK such that a database is + passed as a keyword argument defaulting to *DEFAULT-DATABASE*. + Added docstrings. + +30 Jun 2004 Marcus Pearce + * doc/ref-fdml.xml: document the FDML. + * doc/ref-fddl.xml: move documentation for TRUNCATE-DATABASE here. + * sql/ooddl.lisp: moved *default-varchar-length* to here from + oodml.lisp and renamed to *default-string-length* + +23 Jun 2004 Kevin Rosenberg + * sql/oodml.lisp: Add keyword :transactions to def-view-from-class + to allow specifying transactionless table creation + * doc/ref-oodml.lisp: Add new keyword to signature of + DEF-VIEW-FROM-CLASS + +18 Jun 2004 Marcus Pearce + * Version 2.11.11 + * sql/expressions.lisp: when removing duplicate table identifiers + in the FROM clause of a query, check both table name and alias + are equivalent. + * sql/fdml.lisp: remove DESCRIBE-TABLE. + * sql/db-interface.lisp: remove generics DESCRIBE-TABLE and + DATABASE-DESCRIBE-TABLE. + * sql/package.lisp: remove DESCRIBE-TABLE, DATABASE-DESCRIBE-TABLE + and LIST-TABLE-INDEXES. + * sql/generic-postgresql.lisp: add reader conditional #+nil for + DATABASE-DESCRIBE-TABLE and comment about its uses for + re-implementing LIST-ATTRIBUTE-TYPES with a single SQL query + returning type info for all attributes. + Fix DATABASE-SEQUENCE-LAST. + * sql/fddl.lisp: remove LIST-TABLE-INDEXES and redefine + LIST-INDEXES with additional keyword parameter :ON. + * tests/test-fddl.lisp: replace LIST-TABLE-INDEXES in :fddl/index/3 + with LIST-INDEXES (with :ON parameter). + * doc/global-index.xml: remove DESCRIBE-TABLE and LIST-TABLE-INDEXES. + * doc/ref-connect.xml: minor tidying. + * doc/ref-fddl.xml: document the FDDL. + * doc/TODO: removed items done. Moved note about transaction handling + from TODO to here. + * TODO: move note about transaction handling to doc/TODO. Added + optimization note about LIST-ATTRIBUTE-TYPES and LIST-INDEXES. + +13 Jun 2004 Marcus Pearce + * Version 2.11.10 + * sql/syntax.lisp: updated docstrings. + * sql/package.lisp: exported DATABASE-NAME-FROM-SPEC from CLSQL + package. + * sql/database.lisp: add docstrings for CREATE-DATABASE, + DESTROY-DATABASE, TRUNCATE-DATABASE and LIST-DATABASES. Replace + CLSQL-GENERIC-ERROR signalled in RECONNECT with SQL-CONNECTION-ERROR. + * doc/ref-syntax.xml, doc/global-index.xml: minor tidying. + * doc/ref-connect.xml: document connection/initialisation. + * doc/ref-fdml.xml: move TRUNCATE-DATABASE reference entry here. + * doc/TODO: remove items done. + * Makefile: add db-oracle to to SUBDIRS. + +13 Jun 2004 Kevin Rosenberg + * sql/oodml.lisp: Add new serialization functions: + WRITE-INSTANCE-TO-STREAM and READ-INSTANCE-FROM-STREAM + * sql/expressions.lisp: Avoid duplicate FROM names when selecting + from a table that has more than one primary index. + * sql/conditions.lisp: Fix printing of closed-database error + +13 Jun 2004 Kevin Rosenberg + * Version 2.11.9 + * sql/conditions.lisp: Set initial slot value for message in SQL-WARNING + * sql/transactions.lisp: Correctly set slots of SQL-WARNING + +12 Jun 2004 Marcus Pearce + * sql/package.lisp: export DATABASE-TYPE from CLSQL and subclasses + of SQL-EXPRESSION from CLSQL-SYS. + * sql/syntax.lisp: make error signalled in SQL-OPERATION an + SQL-USER-ERROR. Make SQL-OPERATOR return just one value. + * doc/Makefile: added Mandrake linux. + * doc/catalog-redhat.xml, doc/catalog-mandrake.xml: new files. + * doc/appendix.xml: fixed little typo (adsf). + * doc/glossary.xml: removed closed-database and note about + sql-expression, added some entries. + * doc/ref-syntax.xml: documented the symbolic SQL syntax. + +10 Jun 2004 Kevin Rosenberg + * Version 2.11.8 + * db-mysql/mysql-loader.lisp: Remove load of unnecessary zlib library + * multiple: Add generalized-boolean type as requested by + Edi Weitz + * TODO: Added need for test of generalized-boolean + +9 Jun 2004 Kevin Rosenberg + * Version 2.11.7 released + * uffi/clsql-uffi-loader.lisp: Apply patch from Edi Weitz + for loading with clc-register-user-package. Remove personal + directory from path lisp. + * db-mysql/mysql-loader.lisp: Similar changes + +8 Jun 2004 Kevin Rosenberg + * Version 2.11.6 released + * sql/oodml.lisp: Commit patch from Edi Weitz + to fix symbol writing/reading + * TODO: Add need for symbol slot test + +7 Jun 2004 Marcus Pearce + * sql/package.lisp: remove duplicate export of + *CACHE-TABLE-QUERIES-DEFAULT*. + * doc/ref-*.xml, doc/global-index.xml: new files documenting + the CommonSQL compatible api. + * tests/test-fdml.lisp: modified the test :fdml/transaction/3 to + reflect changes in return values of WITH-TRANSACTION. + +04 Jun 2004 Kevin Rosenberg + * tests/README: Fix filename [reported by Bill Clementson] + * sql/transactions.lisp: Apply return value patch from + Edi Weitz for WITH-TRANSACTION + * tests/README: Remove ptester package requirement (as noted + by Bill Clementson) + +03 Jun 2004 Kevin Rosenberg + * Version 2.11.5 released + * examples/clsql-tutorial.lisp: Remove obsolete :nulls-ok attribute, + Select connection-spec based on connection type. Bugs reported by + Bill Clementson. + * uffi/uffi.dll, uffi/uffi.lib: Recompile with Visual Studio 2003 + * db-mysql/mysql-loader.lisp: Update check for zlibwapi library + name on Windows, add \bin\ directory to search path + +31 May 2004 Marcus Pearce + * db-odbc/odbc-sql.lisp: DB-TYPE replaced with DATABASE-TYPE in + DATABASE-CONNECT. + * sql/operations.lisp: substr now generates SUBSTR for use on + Oracle; added a new operator SUBSTRING for use elsewhere. minus + now generates MINUS for use on Oracle. Nvl signals an error if + not called with exactly 2 arguments. Add concat function for use + instead of the || operator on MySQL and Oracle. + * sql/syntax.lisp: changed internal symbol for the || operator to + CONCAT-OP. + * sql/expressions.lisp: removed redundant and unused functions + GENERATE-SQL (cf. SQL-OUTPUT) and DATABASE-CONSTRAINT-DESCRIPTION + (cf. MAKE-CONSTRAINTS-DESCRIPTION). + * sql/generics.lisp: removed generic function for + DATABASE-CONSTRAINT-DESCRIPTION (see above). + * tests/test-syntax.lisp: modified/added tests according to changes + in sql/operations.lisp. + * tests/test-fdml.lisp: changed SUBSTR to SUBSTRING in test + :fdml/select/21. + * sql/package.lisp: added the operators SQL-SUBSTRING, SQL-CONCAT-OP + and SQL-USERENV to the shared exports list. Removed + ROLLBACK-TRANSACTION, COMMIT-TRANSACTION, DATABASE-START-TRANSACTION, + DATABASE-ABORT-TRANSACTION, DATABASE-COMMIT-TRANSACTION, + TRANSACTION-LEVEL, TRANSACTION, RECORD-SQL-COMMAND and + RECORD-SQL-RESULT from shared exports list. + +30 May 2004 Kevin Rosenberg + * db-postgresql/postgresl-sql.lisp: Avoid computing + result-types lisp when nil result-types. Return only + one value when field-types nil. + * db-mysql/mysql-sql.lisp: Simple queries now + working with prepared statements. + +30 May 2004 Kevin Rosenberg + * Version 2.11.4: MySQL 4.1 now passes all tests + * sql/package.lisp: Add API for prepared statments. + * sql/fdml.lisp: Change implicit flatp processing + for string map-query for greater CommonSQL conformance. + Add high-high API for prepared statements. + * tests/test-basic.lisp: Add test for map-query and + single argument. + * sql/transactions.lisp: Change name/semantics of + autocommit to set-autocommit. + * sql/generic-postgresql.lisp: Add support for + prepared statements. + * tests/test-internal.lisp: New file + * sql/odbc-api.lisp: Update to using ODBC V3 protocol + * clsql-mysql.asd, clsql-uffi.asd: Remove check and Common Lisp + Controller invocation. + * db-mysql/mysql-api.lisp: Add support for MySQL 4.1 field structure + * sql/expressions.lisp: Avoid emitting double parenthesis when a + function contains a subselect. + +27 May 2004 Kevin Rosenberg + * Version 2.11.3 + * sql/ooddl.lisp: Commit patch from Edi Weitz fixing return + type (setf slot-value-using-class) + * TODO: add not that need a test case for the above fix + * db-sqlite: Remove clisp support since clisp can not run CLSQL + with its MOP usage + * db-oracle/oracle-sql.lisp: By default, use OCIEnvCreate as + introduced in Oracle8. Leave older code selectable by a reader macro + for Oracle7 and prior. Avoid use of OCIServerAttach since CLSQL + uses OCILogon and does not the more complex session management. + +26 May 2004 Kevin Rosenberg + * sql/oodml.lisp: Commit universal-time typo patch from Edi Weitz + * test/test-init.lisp: Add universal-time slot to person. + * test/test-fddl.lisp: Add tests of universal-time slot + * test/test-ooddl.lisp: Test universal-time slot in an object + * TODO: Remove need for universal-time test + * debian/rules, debian/control: Add cl-sql-oracle binary package + * doc/appendix.xml: Add Oracle backend information + * db-oracle/oracle-objects.lisp: Add database-get-type-specifier for + universal-time. Convert BIGINT CLSQL type to CHAR SQL type + * db-mysql/mysql-sql.lisp: Fix condition name to sql-connection-error + * doc/ref-clsql.xml: Renamed from ref_clsql.xml. Change the documentation + for map-query to reflect changed in arguments to be CommonSQL compatible. + Updated old clsql conditions to new CommonSQL compatible conditions. + +25 May 2004 Kevin Rosenberg + * sql/oodml.lisp: (string n) now produces a CHAR field. Add new VARCHAR + type. Added *default-varchar-length* rather than previous hard-coded + varchar length. Remove 'simple-string and 'simple-base-string since they + are subtypes of 'string. + * db-oracle/oracle-sql.lisp: Use *default-varchar-length* rather than + local hard-coded value. + * sql/metaclasses.lisp: Convert specified type VARCHAR and (VARCHAR n) to Lisp + type string. Convert specified-type (CHAR n) to string. Convert specified-type + CHAR to lisp type character. + * sql/generic-postgresql.lisp: (string n) => (CHAR n) + * sql/operations.lisp: Add userenv + * doc/TODO: Add AUTOCOMMIT. Remove need for large table and bigint + slot tests + * sql/oracle-sql.lisp: Add 64-bit bigint direct conversion + * uffi/clsql-uffi.lisp: Handle signed 64-bit integers + * test/test-init.lisp: Add large table with bigint slot + +25 May 2004 Kevin Rosenberg + * Version 2.11.1 released: Much simpler Oracle client library loading. + Now uses ORACLE_HOME environmental variable as well as tests default + path for Oracle Instant Client. + +25 May 2004 Kevin Rosenberg + * Version 2.11.0 released: Full Oracle support. All tests pass + * db-oracle/oracle-sql.lisp: Add declaration so that SBCL runs efficiently. + * tests/test-init.lisp: capitalize odbc backend name in banner + * CONTRIBUTORS: Add note about Marcus' excellent work + * sql/oodml.lisp: Removed old stub function + * clsql.asd: Use module names in current package rather than keyword package + * db-oracle/oracle-sql.lisp: Don't trim trailing spaces. Prevent interrupts + in setting sequence position. Make autocommits more efficient. + * tests/test-init.lisp: Skip 2 tests on Oracle which have unsupported syntax + * sql/oodml.lisp: Get rid of undocumented raw-string type. CommonSQL + strings are raw (non-trimmed trailing whitespace). Add database-get-type-specifier + and read-sql-value for NUMBER and CHAR. + * sql/base-classes.lisp: Add autocommit slot + * sql/transaction.lisp: Added autocommit processing, mild cleaning. + * doc/intro.xml: Add Oracle + +24 May 2004: Marcus Pearce (m.t.pearce@city.ac.uk) + * db-postgresql-socket/postgresql-socket-sql.lisp: replace + CLSQL-SIMPLE-WARNING with SQL-WARNING. + * db-sqlite/sqlite-sql.lisp: replace CLSQL-SIMPLE-WARNING with + SQL-WARNING. + * db-aodbc/aodbc-sql.lisp: replace CLSQL-ERROR with SQL-ERROR. + * clsql.asd: reworked module structure in package definition and + file names to better reflect component functionality. + * sql/package.lisp: added SQL-FATAL-ERROR and SQL-TIMEOUT-ERROR to + exports list. Removed duplicate and obsolete exports. Exported + remaining SQL operations: SQL-SOME, SQL-<>, SQL-BETWEEN, SQL-DISTINCT, + SQL-NVL and SQL-FUNCTION. Organised exports by functionality/file and + according to whether they are specified by CommonSQL or CLSQL + extensions. + * sql/transaction.lisp: replace CLSQL-SIMPLE-WARNING with + SQL-WARNING. + * sql/generics.lisp: moved generics for QUERY and EXECUTE-COMMAND + here from basic-sql.lisp. + * sql/expressions.lisp: NEW FILE: renamed from classes.lisp (deleted). + * sql/fddl.lisp: NEW FILE: renamed from table.lisp (deleted). + * sql/fdml.lisp: NEW FILE: merger of basic-sql.lisp and sql.lisp + (both deleted). + * sql/ooddl.lisp: NEW FILE: ooddl from objects.lisp (deleted). + * sql/oodml.lisp: NEW FILE: oodml from objects.lisp (deleted). + +23 May 2004 Kevin Rosenberg + * Version 2.10.22 released + * sql/kmr-mop.lisp, sql/objects.lisp: Since SBCL is the only implementation that + has reversed class slots, change the default for ordered-class-slots so that slots + are now in the same order an in the def-view-class. + * sql/sql.lisp: Honor case of string table identifier to INSERT-RECORDS + * test/test-fddl.lisp: Add two tests for mixed case names + * db-oracle/oracle-sql.lisp: Add missing database qualifier. Return NUMBER (double) + for computed fields, this will require some adjustment to the test suite which + in many cases expects integers. + * test/test-fdml.lisp: Accomodate that Oracle returns doubles for computed columns + +22 May 2004 Kevin Rosenberg + * Version 2.10.21 released + * sql/sequences.lisp: Move generic sequence functions here from db-sqlite, + db-odbc, and db-aodbc. + * sql/*.lisp: Add db-type parameter to generic functions READ-SQL-VALUE, + DATABASE-GET-TYPE-SPECIFIER, and OUTPUT-SQL-VALUE-AS-TYPE. Update methods to use these. + * sql/generic-postgresql.lisp, sql/generic-odbc.lisp: New files + * db-odbc/odbc-sql.lisp, db-aodbc/aodbc-sql.lisp: Move common code to + sql/generic-odbc.lisp + * db-postgresql/postgresql-sql.lisp, db-postgresql-socket/postgresql-socket-sql.lisp: + Move common code to sql/generic-postgresql.lisp + * sql/classes.lisp: honor case of string tables when outputting queries + * sql/objects.lisp: Add database type to default database-get-type-specifier method + * sql/sql.lisp: Add database type to default database-abort-transaction method + * db-mysql/mysql-objects.lisp: New file + * sql/objects.lisp: Move MySQL specific code to mysql-objects.lisp + * sql/utils.lisp: Add GETENV function which will be used to get ORACLE_HOME + from environment + * test/test-fdml.lisp: String table names are now case sensitive, so convert to + default db case for FDML/SELECT/25 + +22 May 2004 Kevin Rosenberg + * Version 2.10.20 released: Oracle backend now fails 6 out of 200 tests + * TODO: Added 2 variances from CommonSQL. Add tests for owner phrases + and string identifiers with non-default case + * sql/table.lisp: Don't convert string table name to a symbol. + * sql/classes.lisp: Honor case of string identifiers + * sql/sql.lisp: Ensure recyclebin is purged for Oracle in + TRUNCATE-DATABASE + * db-oracle/oracle-sql.lisp: Add sequence functions, fix use of + of owner phrases. Obtain server and client versions. + * db-oracle/oracle-objects.lisp: Fix type specifiers + * tests/test-fddl.lisp: Allow :varchar2 and :number as data types + * tests/test-init.lisp: Properly get username from Oracle connection-spec + +22 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/generics.lisp: reworked docstrings. Remove generics for + ADD-TO-RELATION and REMOVE-FROM-RELATION. + * sql/objects.lisp: reworked docstrings. Changed UPDATE-OBJECT-JOINS + to UPDATE-OBJECTS-JOINS for CommonSQL compatibility. + * sql/package.lisp: Changed UPDATE-OBJECT-JOINS to UPDATE-OBJECTS-JOINS + for CommonSQL compatibility. Remove ADD-TO-RELATION and + REMOVE-FROM-RELATION. + * tests/test-oodml.lisp: Changed UPDATE-OBJECT-JOINS to + UPDATE-OBJECTS-JOINS for CommonSQL compatibility. + * doc/TODO: added notes about extra slot options to DEF-VIEW-CLASS. + * sql/conditions.lisp: add documentation for conditions. Add + SQL-TIMEOUT-ERROR and SQL-FATAL-ERROR for CommonSQL compatibility. + +21 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/basic-sql.lisp: reworked docstrings. + * sql/transactions.lisp: reworked docstrings. + * sql/sql.lisp: reworked docstrings. + * sql/initialize.lisp: reworked docstrings. INITIALIZE-DATABASE-TYPE + sets *DEFAULT-DATABASE-TYPE* for CommonSQL compatibility. + * sql/database.lisp: reworked docstrings. + * doc/TODO: added notes about START-TRANSACTION and IN-TRANSACTION-P + and FDML extensions and database extensions. + +20 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * db-oracle/oracle-sql: Use clsql-specific error conditions. Use owner keyword. + * db-oracle/make9.sh: add makefile for building with Oracle 9 client + libraries + * sql/table.lisp: Add logic for dealing with Oracle 10g vs. previous + Oracle versions with the PURGE option required for drop table. This needs + to be converted to a generic function and moved to db-oracle/oracle-sql.lisp + +20 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/classes.lisp: remove unused PARAMS slot in SQL-IDENT-ATTRIBUTE. + * sql/syntax.lisp: remove unused PARAMS keyword arg to SQL-EXPRESSION. + * sql/table.lisp: reworked docstrings. + * sql/objects.lisp: moved *default-update-objects-max-len* here from + table.lisp. + * doc/TODO: notes about :if-does-not-exist arg to DROP-TABLE, + DROP-VIEW and DROP-INDEX and the use of the :transactions and + :constraints keyword args to CREATE-TABLE. + * sql/classes.lisp: the DESCRIPTION argument to CREATE-TABLE is now + CommonSQL compatible with respect to column constraints. + +20 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * sql/oracle-sql.lisp: Now compiles and runs on SBCL. + Requires UFFI 1.5.0 or higher + +20 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.19 + * sql/conditions.lisp: Fix cerror + +19 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.18 released: New condition hierarchy to be compatible + with CommonSQL -- not backward compatible with previous CLSQL. + * sql/db-interface.lisp: Add more default methods + * sql/objects.lisp: Add explicit table name to order-by parameters + in find-all when only one table to avoid selecting a duplicate row. + Fix error in FIND-ALL when using :order-by such as (([foo] :asc)) + as previous logic was adding two fields (foo asc) to SELECT query. + Make :result-types :auto be the default for object selections. + Properly handle caching key when using multiple order-by with asc/desc + directions. + * db-oracle/*.lisp: Much improvements, now passes 90% of test suite + +19 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/recording.lisp: reworked docstrings. + * sql/syntax.lisp: reworked docstrings. + * doc/TODO: added notes about extensions to SQL-RECORDING-P and the + SQL syntax state functions being macros. + +19 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * sql/package.lisp: Export initialize-database-type and + *initialize-database-types* from CLSQL package. + * sql/conditions.lisp: Add new CommonSQL compatible conditions, + remove old CLSQL conditions. + * sql/loop-extensions.lisp: Make errors of type sql-user-error + * */*.lisp: Convert to from old to new conditions + +18 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * sql/table.lisp: Add PURGE to drop command for oracle 10g backend. + To handle this difference, will need to add a new database-drop-table + generic function. + * db-oracle/oracle-sql.lisp: Move server-version and + major-version-number to database object to allow multiple connections + to Oracle servers of different versions. + +18 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * TODO: moved notes about backends to doc/TODO. + * doc/TODO: added notes about backends and select extensions. + * sql/base-classes.lisp: remove obsolete schema slot in database + class. + +16 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * db-oracle/oracle-api: Add OCIServerVersion + * db-oracle/oracle-sql: Query and store server version on connect + * sql/db-interface.lisp: Add new db-type-has-bigint? generic + function to handle OCI's lack of bigint support + * test/test-basic.lisp: Separate bigint testing + * test/test-utils.lisp: Add oracle to specs and list of backends + * doc/TODO: New file + * test/test-fdml.lisp: Added FDML/SELECT/34 to test + run-time instantiation of variables in reader macros. + * TODO: Remove item already complete. Add note about + condition variances + +16 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/syntax.lisp: added condition to the reader macro to treat [*] + as a column identifier (rather than an operation) for CommonSQL + compatibility. + * tests/test-fdml.lisp: add tests for ORDER-BY and SET-OPERATION + keword args to SELECT, [*] as column identifier, new MAP-QUERY + behaviour and the ANY and ALL operators in subqueries. + * tests/test-init.lisp: add set-operation and subquery tests to + appropriate skip lists. + * sql/objects.lisp: remove redundant and non CommonSQL compatible + ORDER-BY-DESCENDING keyword argument for SELECT. + * sql/classes.lisp: remove redundant and non CommonSQL compatible + ORDER-BY-DESCENDING keyword argument for SELECT. + * tests/test-oodml.lisp: add test for ORDER-BY keyword to SELECT + with object queries. + +15 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * sql/db-interface.lisp: Add new db-type-has-union? + since Mysql 3.23 does not support unions. + * sql/test-init.lisp: Don't try union tests on database + backends which do not support it. + * db-oracle/*.lisp: initial port to UFFI + * sql/objects.lisp: implement UPDATE-OBJECT-JOINS, + implement REFRESH for SELECT. + * tests/test-oodml.lisp: Add tests for deferred retrieval, + caching, refresh, and update-object-joins + * tests/test-init.lisp: Add deferred-employee-address class + +15 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/operations.lisp: make MINUS operator a synonym for EXCEPT. Add + COALESCE operator and make NVL a synonym for this. Make ANY, SOME, + ALL and EXISTS generate function expressions so they output the + correct SQL. + * sql/classes.lisp: SELECT now generates appropriate SQL when + passed the SET-OPERATION and ALL keyword arguments. + * sql/classes.lisp: the ORDER-BY keyword argument to SELECT now + accepts ordering constraints as pairs of the form (column direction) + where direction may be :ASC or :DESC. + * tests/test-syntax.lisp: added tests for MINUS and COALESCE/NVL. + Correct tests for ANY, SOME, ALL and EXISTS. + * tests/test-fdml.lisp: added test for COALESCE. + * sql/sql.lisp: MAP-QUERY now applies FUNCTION to QUERY-EXPRESSION + using funcall unless QUERY-EXPRESSION returns one column and its + FLATP slot is not nil in which case apply is used. + * tests/test-basic.lisp: modified calls to MAP-QUERY to reflect the + changes. + * TODO: remove items done. + * db-postgresql/postgresql-sql.lisp: no need to reverse results in + DATABASE-LIST-ATTRIBUTES. + * db-postgresql-socket/postgresql-socket-sql.lisp: no need to reverse + results in DATABASE-LIST-ATTRIBUTES. + +15 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/classes.lisp: SELECT now accepts table identifiers as strings + for CommonSQL compliance. Add support for qualified sql identifiers + with aliased table names. + * tests/test-fdml.lisp: added tests for table identifiers as strings + in SELECT and for aliased definitions. + * tests/test-syntax.lisp: added tests for alias definitions. + +15 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/sql.lisp: PRINT-QUERY now calls QUERY with result-types and + field-names set to nil. + * sql/sql.lisp: PRINT-QUERY now computes column sizes correctly + with null attribute values. + * sql/operations.lisp: modify SQL concatenation operator to accept + unescaped || symbol. + * sql/syntax.lisp: modify sql reader macro function to accept + unescaped sql concatenation operator. + * tests/test-fdml.lisp: unescape sql concatenation operator. + * tests/test-syntax.lisp: unescape sql concatenation operator. + * TODO: remove items done. Add notes about SQLITE/MYSQL backends. + Note to add test for universal-time. Note about difference from + CommonSQL in transaction handling. + +13 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * tests/test-init.lisp: Add deferred-employee-address + class + * tests/test-oodml.lisp: Add deferred retrieval testgs + +12 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.17 + * LATEST-TEST-RESULTS: Run on all platforms, add AMD64 + * sql/sql.lisp: Add FOR-EACH-ROW macro from clsql-classic/sql.lisp + * db-sqlite/sqlite-uffi-api.lisp: Fix row-pointer type + * *: Fix minor style warnings + * clsql-classic: Remove system and subdirectory + * clsql-base: Remove system and subdirectory and + fold into clsql system + * doc/intro.xml: Remove reference to old clsql-base. Add x86_64 + as supported platform. + +12 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.16: CLSQL now fully supports AllegroCL AMD64 + * db-odbc/odbc-api.lisp: work around return-type bug [spr28889] in + Allegro 7.0beta AMD64 + * db-odbc/*.lisp: Add a layer of indirection to foreign-type + of ODBC longs since this type can vary on 64-bit platforms depending + upon the compilation options of unixODBC. + * db-mysql/mysql-api.lisp: Fix int vs. long slots in foreign + structures found by testing with AllegroCL 7.0b AMD64. + * db-*/*-loader.lisp: Load 64-bit libraries on 64-bit platorms + * sql/objects.lisp: Simple implementation of UPDATE-OBJECT-JOINS. + Initial caching support for SELECT + * tests/test-oodml.lisp: Avoid using cache when testing select. + * sql/kmr-mop.lisp: Explicitly check slot order and + store as a cl:*feature* + * sql/recording.lisp: Remove additional types to + increase CommonSQL conformance. + * tests/test-init.lisp: Change a :column attribute + to test symbols as value + * sql/relations.lisp: Remove functions since they don't support + many to many relationships. + * examples/clsql-tutorial.lisp, doc/csql.lisp: Remove use + of add-to-relations function and replace with explicit field settings. + * base/classes.lisp: Remove obsolute query-stream. Add record-caches slot. + +9 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.15 + * LATEST-TEST-RESULTS: results with current version + * sql/kmr-mop.lisp: Make CMUCL reader macros specific for cmu18 + since cmu19 has opposite order of class slots. + * sql/objects.lisp: Fix (setf slot-value-using-class) for Lispworks + * tests/test-fdml.lisp: Renumber SELECT tests to avoid overwriting + a previous test + * tests/test-init.lisp: Check test-database-underlying-type for + ODBC/MySQL tests + +8 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/operations.lisp: complete remaining operations for the sql + syntax: SUBSTR, SOME, ORDER-BY, GROUP-BY, NULL, DISTINCT, EXCEPT, + UNION, INTERSECT, BETWEEN. + * sql/classes.lisp: add new classes: SQL-BETWEEN-EXPRESSION, + SQL-QUERY-MODIFIER-EXPRESSION and SQL-SET-EXPRESSION. + * tests/test-syntax.lisp: add tests for new operations. + * tests/test-fdml.lisp: add tests for queries based on new operations. + * tests/test-init.lisp: add select/20 to tests skipped for sqlite and + select/20, query/5, query/7 and query/8 to tests skipped by mysql. + * TODO: removed entries done. + +8 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * tests/benchmarks.lisp: Add immediate vs. deferred + join test. + +8 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.13: Now works on openmcl 0.14.2 + * sql/objects.lisp: Add :retrieval :immediate for + object selections + * tests/test-init.lisp: Add non-index fields for testing + join class employee-addresss + * test/test-oodml.lisp: Add tests for retrieval immediate + * sql/metaclasses.lisp: Handle differences in direct-slot-definition + values which are now listifed by openmcl 14.2. + * sql/objects.lisp: more framework for supporing immediate retrieval + +7 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * docs/intro.xml: Upload location of a README file + * sql/metaclass.lisp: Work-around openmcl's CHANGE-CLASS + changing the type-specifier. Use a lisp type of (OR NULL FOO) + for a specified-type of FOO unless :db-constraints :not-null. + No need to specialize finalize-inheritance for openmcl. + * tests/test-*.lisp: Rename fields so that joins occur on + fields with different names. This ensures that join code is + selecting the proper name. + * test/test-init.lisp: Add :base-table for employee-address + view class for testing. + * sql/objects.lisp: Use view-table rather than name of table + in a number of places to fix errors noted with using :base-table. + +6 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/objects.lisp: replace *update-records-on-make-instance* with + *db-auto-sync* which also controls both automatic creation of + new records on creation of new instance and updating of record + fields on setting of instance slots (as suggested by Edi Weitz). + * tests/test-init.lisp: replace *update-records-on-make-instance* + with *db-auto-sync*. + * sql/package.lisp: replace *update-records-on-make-instance* + with *db-auto-sync*. + * TODO: replace *update-records-on-make-instance* with *db-auto-sync*. + * sql/objects.lisp: remove redundant rebindings of *db-initializing* + and *default-database* in FIND-ALL. + * sql/package.lisp: import time functions from CLSQL-BASE. + * tests/test-time.lisp: replace CLSQl-BASE package qualifier with CLSQL. + * tests/test-fdml.lisp: replace CLSQl-BASE package qualifier with CLSQL. + * tests/test-init.lisp: replace CLSQl-BASE package qualifier with CLSQL. + * tests/test-ooddl.lisp: replace CLSQl-BASE package qualifier with + CLSQL. + +4 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * sql/classes.lisp: Add SQL-OBJECT-QUERY type. Have [select 'class] + now return a sql-object-query type rather than directly performing a query. + This improves CommonSQL conformance. + * sql/sql.lisp: Add new QUERY method for SQL-OBJECT-QUERY. Move + from basic/basic-sql.lisp the DO-QUERY and MAP-QUERY since they now + depend on sql-object-query-type. + * sql/loop-extensions.lisp: Move from base package + * classic/package.lisp: remove references to map-query and do-query + +4 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * TODO: New section on optimizations, especially optimizing JOINs. + * sql/objects.lisp: Have :target-slot return of list of lists rather + than a list of cons pairs to be conformant with CommonSQL. + Make :target-slot much more efficient by using a SQL inner join + statement and just requiring one SQL query. Add :retrieval :deferrred + to target-slot joins. Add placeholder for update-objects-join. + * sql/classes.lisp: Add :inner-join and :on slots to sql-query class + and process them for query output-sql. + +4 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.11 + * base/basic-sql.lisp: Avoid multiple evaluation + of query-expression in DO-QUERY + * sql/objects.lisp: Make SELECT a normal function. + SELECT now accepts type-modified database identifiers, such as + [foo :string] which means that the values in column foo are returned + as Lisp strings. Add new *update-records-on-make-instance* special + variable controlling automatic creation of new instances. Add missing + RESULT-TYPES keyword to FIND-ALL. Add :target-slot support. + * sql/packages.lisp: Export *update-records-on-make-instance* + * test/test-oodml.lisp: Add tests for :target-slot and many-to-many + selections. + * test/test-fdml.lisp: Add tests for type-modified + database identifiers. + * test/test-init.lisp: Stop using add-relation since implementing + many-to-many joins. Use *update-records-on-make-instance* + to automatically store records on instance creation. Add many-to-many + employee-address view-class. + +4 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.10 + * base/loop.lisp: Add object iteration. Use :result-type + :auto for result-set. Remove + duplicate (and non-correct) code for non-list variables by + simply making an atom variable into a list. + * sql/package.lisp: Remove unnecessary clsql-sys package + and replace it with clsql. + * sql/metaclasses.lisp: Properly store specified-type from + direct-slot-definition and then store translated type in + effective-slot-definition + * sql/classes.lisp: Don't output type in sql-output + for SQL-IDENT-ATTRIBUTE. This is in preparation for supporting + [foo :integer] as fields in SELECT. + * sql/query.lisp: Set default for :result-types to :auto in + FDML QUERY. + * sql/objects.lisp: Use specified-type when invocating + database-get-type-specifier. def-view-class macro now returns + the class instance. + * base/basic-sql.lisp: Make :AUTO the default value for + :RESULT-TYPES for MAP-QUERY and DO-QUERY. + * sql/objects.lisp: Add bigint type + * test/tests-basic.lisp: Add tests for :result-types for + MAP-QUERY and DO-QUERY + * test/test-fdml.lisp: Add test for result-types in LOOP + and also using single symbol rather than a list for variables. + Add test that default :result-types is auto for FDML QUERY. + * test/test-syntax.lisp: Don't expect TYPE in the SQL-OUTPUT + of SQL-IDENT-ATTRIBUTE. + * test/test-oodml.lisp: Enable OO loop iteration test, + modify it so it doesn't depend on boolean where. + +4 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * Version 2.10.9 + * sql/objects.lisp: added derived type specifier for universal time. + * sql/package.lisp: added #:universal-time to clsql-sys exports. + * tests/test-oodml.lisp: added test for translation of boolean slots + in SELECT with object queries. + +3 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * db-odbc/odbc-api.lisp: Fix changing nil to "NIL" + for odbc/postgresql backend. + * db-odbc/odbc-sql.lisp: Fix ATTRIBUTE-TYPE so that + it can handle NIL values from the ODBC driver + * tests/benchmarks.lisp: New file with initial + benchmark suite + * sql/relations.lisp: fix to add subclassing support, + minor optimizations [Edi Weitz] + +3 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.8 + * base/conditions.lisp: Add *backend-warning-behavior* + special variable. + * db-postgresql-socket/postgresql-socket-sql.lisp: + Honor value of *backend-warning-behavior* + * tests/test-fdml.lisp: Remove test of raw boolean value + since different backends handle this differently. Add + test for :column attribute. + * tests/test-oodml.lisp: Add tests for boolean slot value + and for :void-value attribute + * tests/test-init.lisp: Use *backend-warning-behavior* + to suppress warnings from postgresql about implicitly + creating primary key in tables. Add new address table. + +3 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.7 + * db-odbc/odbc-dbi.lisp: Convert TINYINT to integers when + result-types is :auto + * sql/objects.lisp: Properly handled writing/reading Boolean + values from SQL database when retrieving objects. + * test/test-fdml.lisp: Add another test for boolean results + * test/utils.lisp: Fix incorrect declaration + +2 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * Version 2.10.6 + * sql/generics.lisp: add generic function for SELECT. + * sql/objects.lisp: make SELECT a method specialisation. + * sql/classes.lisp: MAKE-QUERY now calls SELECT if the selections + referred to are View Classes. + * base/basic-sql.lisp: in DO-QUERY and MAP-QUERY, if the + query-expression arg evaluates to a list, then we have an object + query. + * tests/test-oodml.lisp: add tests for DO-QUERY and MAP-QUERY with + object queries. + * TODO: remove items done and add a todo for SELECT. + * sql/objects.lisp: SELECT takes a :field-names arg to pass on to + QUERY. + * sql/sql.lisp: add :field-names arg to QUERY. + * tests/test-fdml.lisp: minor rework to use :field-names arg to + SELECT. + +2 May 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * sql/objects.lisp: fix bug in FIND-ALL when SELECT called with 2 + or more View Classes. + * sql/objects.lisp: make the :flatp argument to SELECT work with + object queries. + * sql/objects.lisp: make SELECT accept a :result-types argument + (defaults to :auto) which is passed on to QUERY. + * sql/objects.lisp: SELECT returns field-names as a second value. + * tests/test-ooddl.lisp: add flatp arg to SELECT calls as appropriate. + * tests/test-fdml.lisp: add flatp/result-types arguments to calls + to SELECT and take only first value as appropriate. + * tests/test-fdml.lisp: add two new tests for query result coercion + and the field-names returned as a second value from SELECT. + * tests/test-oodml.lisp: add flatp arg to SELECT calls as appropriate. + +1 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.6-pre1 + * sql/metaclasses.lisp: Add void-value slot + * doc/csql.xml: Update def-view-class documentation + * test/test-init.lisp: Change old :db-type to :db-kind. + Remove old :nulls-ok attributes. + * sql/objects.lisp: Add new universal-time and bigint + types. Optimize reading of integers using parse-integer + rather than read-from-string. + * */*.lisp: Merge clsql-base-sys and clsql-base packages + into clsql-base package + * classic/sql.lisp: Move large object support into base, leaving + classic without any functionality that is provided in the clsql + system. + * classic/package.lisp: Rename clsql-classic-sys package to + its former nickname of clsql-classic + +1 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.5: SQLite backend now passes all result-types tests + * clsql-sqlite.asd: Depend on clsql-uffi system + * db-sqlite/sqlite-sql.lisp: Use clsql-uffi:convert-raw-field + for efficiency and code reuse. + * db-sqlite/sqlite-api-uffi.lisp: Change (* :char) to (* :unsigned-char) + for better cross-implementation compatibility. + +1 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.4 + * sql/tables.lisp: Fix typo in CACHE-TABLE-QUERIES + [Marcus Pearce] + * db-postgresql/postgresql-sql.lisp: Fix foreign-string vs. cstring + bug on SBCL in result-field-names function as reported by Marcus Pearce + * db-sqlite/sqlite-sql.lisp: Fix in database-store-next-row + manifest in SBCL testing + +1 May 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.3 + * sql/database.lisp: Conform more to CommonSQL output + for STATUS command [Marcus Pearce] + * sql/sqlite-sql.lisp: Rework to use result-types + * sql/sqlite-api-clisp.lisp: Add compatibility layer + with sqlite-api-uffi.lisp so that sqlite-sql.lisp can + be cleaned up of most clisp reader conditionals + * sql/test-init.lisp: Now run field type tests on sqlite + backend + +30 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.2 + * base/basic-sql.lisp: Set default value of :result-types + to :auto for more CommonSQL conformance. + * test/test-fdml.lisp: Add tests for numeric value of fields + + +30 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.10.1: New API function: CACHE-TABLE-QUERIES. + * base/basic-sql.lisp, db-*/*-sql.lisp: More CommonSQL conformance. + Return field names as second value for QUERY. This can be overridden + for efficiency with the new keyword :FIELD-NAMES set to NIL + in the QUERY invocation. + * test/test-fdml.lisp: Add tests for new field-name feature + * sql/metaclass.lisp: Remove old Lispworks cruft + and replace it with invocation of new code in kmr-mop.lisp + which actually works with Lispworks 4.2 + * doc/ref_clsql.xml: Document new :FIELD-NAMES keyword to + QUERY function + * base/db-interface.lisp: Document the multiple values + returned by DATABASE-ATTRIBUTE-TYPE so matches the + undocumented CommonSQL behavior. + * sql/table.lisp: Add *CACHE-TABLE-QUERIES-DEFAULT* and + *DEFAULT-UPDATE-OBJECTS-MAX-LEN* variables and export them. + LIST-ATTRIBUTE-TYPES now conforms to CommonSQL spec. + Implement CACHE-TABLE-QUERIES. + * db-odbc/odbc-sql.lisp: Fix attribute-type function + * test/test-fddl.lisp: Add tests for attribute type + * db-mysql/mysql-sql.lisp: Mild optimization in accessing + field structures. + * base/classes.lisp: Add attribute-cache slot to database clas + * base/initialize.lisp: initialize-database-type now automatically + loads database-type backend as needed. + * base/test-init.lisp: Utilize new initialize-database-type functionality. + * TODO: remove items done + +30 Apr 2004 Marcus Pearce (m.t.pearce@city.ac.uk) + * Version 2.9.6 + * sql/objects.lisp: remove create/drop-sequence-from-class. + * sql/objects.lisp: add INSTANCE-REFRESHED generic function. + * sql/objects.lisp: improved CommonSQL compatibility for + UPDATE-RECORD-FROM-SLOT, UPDATE-RECORD-FROM-SLOTS, + UPDATE-RECORDS-FROM-INSTANCE and DELETE-INSTANCE-RECORDS. + * sql/generics.lisp: move generics from objects.lisp to here. + +29 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.6-pre1 + * db-mysql/mysql-client-info.lisp: Add client version 4.1 + detection + * sql/sql.lisp: Make *default-database* the default for + TRUNCATE-DATABASE + +28 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.5 + * db-mysql/mysql-sql.lisp: Fix bug in transaction capability + detection + * sql/objects.lisp: Commit patch from Slawek Zak to allow specifying + :metaclass in DEF-VIEW-CLASS invocation. This allows defining classes + on a metaclass specialized from standard-db-class. + + +24 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.4: Multiple changes to support Allegro's "modern" + lisp which uses a lowercase reader and has case-sensitive symbols + * sql/classes.lisp: Fix make-load-form bug for sql-ident-table + exposed by case-sensitive mlisp. + +22 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.3: All tests now pass on all platforms! + * LATEST-TEST-RESULTS: New file with summary of test results + * sql/generics.lisp: New file for generic function definitions. + * test/test-init.lisp: Display names of skipped tests. + Use unwind-protect to ensure disconnect + * sql/objects.lisp: Change database-type to database-underlying-type + so that actual database engine is properly identified + * db-odbc/odbc-api.lisp: Have default *time-conversion-function* + return an ISO timestring for compatibility with other drivers. + Workaround bug in MyODBC for LIST-TABLE-INDEXES + * test/test-fdml.lisp: Accomodate that odbc-postgresql driver + returns floating-point values for floor and truncate operations + * db-aodbc/aodbc-sql.lisp: Implement DATABASE-LIST-VIEWS + * tests/test-basic.lisp: Port to regression tester + * test/test-init.lisp: Output to *report-stream* + * docs/appendix.xml: Document ODBC and SQLite backends. + * sql/classes.lisp: Make output-sql require a database parameter. + This allows SQL generation to have the proper case to support + the differences in case handling between CommonSQL API, + Postgresql, MySQL, Oracle. + +21 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.2: Improvments in database capability introspection + and querying. Support transactions in MySQL where available. + All tests now pass on MySQL and SQLite in addition to postgresql + and postgresql-socket. ODBC fails only with OODDL/TIME/1 and OODDL/TIME/2. + * db-odbc/odbc-sql.lisp: Add DATABASE-LIST-VIEWS. Better support + DATABASE-LIST-SEQUENCES. + * clsql-uffi.asd, clsql-mysql.asd: Improve shared library loading + * Database_capabilies: add HAS-VIEWS, HAS-CREATE/DESTROY-DB, + HAS-BOOLEAN-WHERE, TRANSACTION-CAPABLE + * tests/*.lisp: Check database capabilities and remove tests which + the database backend does not support + * sql/table.lisp: Add :TRANSACTIONS keyword to create table which + controls whether InnoDB tables will be created when supported on + the underlying MySQL server. + +20 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.9.0: New API function: LIST-TABLE-INDEXES, + supported by all database backends (except AODBC since + AODBC doesn't support index querying) + * db-obdc/odbc-sql.lisp: Support DATABASE-LIST-INDEXES + * db-odbc/odbc-api.lisp: Add %TABLE-STATISTICS function + to support index queries + * db-aodbc/aodbc-sql.lisp: Filter driver manager + "information_schema" tables from LIST-TABLES + * tests/test-basic.lisp: Remove table after testing + * tests/test-fddl.lisp: Test LIST-TABLE-INDEXES + * base/db-interface.lisp: Add DATABASE-UNDERLYING-TYPE + which gets the underlying type of database -- required + when dealing with ODBC databases and want to query + database capabilities. Added DB-USE-COLUMN-ON-DROP-TABLES? + as first database-backend specific feature. Is T on + :mysql, NIL on other backends. Change DROP-TABLE to + query this. + +19 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.8.2: Build changes for FreeBSD [Slawek Zak] + +19 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.8.1 + * db-odbc/odbc-sql.lisp: Add DATABASE-LIST function + * db-odbc/odbc-dbi.lisp: Add LIST-ALL-DATA-SOURCES function + +19 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.8.0: New API function: LIST-DATABASES + * base/utils.lisp: Fix command-output on CMUCL/SBCL + * db-*/*-sql.lisp: Add new database-list function + * base/database.lisp: Add new LIST-DATABASES command + +18 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.9 + * db-sqlite/sqlite-sql.lisp: Fix sequence functions. + * db-sqlite/sqlite-api-uffi.lisp: Print error string + correctly. + +18 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.7 + * doc/csql.xml, examples/clsql-tutorial.lisp: Patch for db-kind + from Eduardo Munoz. + +17 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.6 + * base/objects.lisp, base/classes.lisp: Patch + for db-kind from Eduardo Munoz + +16 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.5 + * base/basic-sql.lisp: Fix FLATP in QUERY + +16 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.3: Implement RECONNECT + +15 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.2: Fix ODBC on Lispworks Windows + +15 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.1: Fix for new ODBC backend. + clsql-odbc now works on SBCL, CMUCL, OpenMCL + in addition to AllegroCL and Lispworks. + +15 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.7.0: New backend: ODBC. Tests as + well as AODBC backend on Allegro,Lispworks. + SBCL and CMUCL don't work quite yet. Requires UFFI v1.4.11+ + * db-odbc/*.lisp: Add ODBC3 function SQLSetEnvAttr + to explicitly set ODBC2 support. Add BIGINT support. + Add result-types support. Added SQLTables. + Fix array type in fetch-all-rows. Make width + changable by database or query. + * base/utils.lisp: Add process functions + * base/package.lisp: Export utils to CLSQL-BASE-SYS + * db-aodbc: Implement sequence functions, + database-list-tables, database-list-attributes + * tests/utils.lisp: Add support for ODBC backend, + rework READ-SPECS to use +all-db-types+ + * db-mysql/mysql-sql.lisp: Use WITHOUT-INTERRUPTS + for SEQUENCE-NEXT + +13 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.13. Requires UFFI version 1.4.9 + * db-odbc/*.lisp: Further porting. + Pre-alpha code! But, basic query is now working. + +13 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.12 + * base/transactions.lisp: Add quote for macro + expansion of WITH-TRANSACTIONS [Time Howe] + * db-sqlite/sqlite-sql.lisp: Support memory database + in database-probe [Ng Pheng Siong] + * db-odbc/*.lisp: Initial port to UFFI of SQL-ODBC. + The DBI layer is not finished. + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.11 + * sql/objects.lisp: add :root-class functionality for + list-classes and add duration type support [Marcus Pearce] + * db-odbc: Add mid-level [DBI] layer + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.10 + * db-aodbc: Add methods for generic functions, some are + not yet implemented. + * clsql-odbc.asd, db-odbc/*.lisp: Initial start of ODBC + support + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.9 + * base/package.lisp: Add missing symbols [Marcus Pearce] + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.8 + * test/test-fddl.lisp: Cleanup fix [Marcus Pearce] + * utils/time.lisp: Multiple fixes [Marcus Pearce] + * sql/sql.lisp: Fix for truncate-database [Marcus Pearce] + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.7 + * sql/*.lisp: Remove schema versioning cruft + [Marcus Pearce] + * Makefile: Add classic subdirectory + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.6 + * sql/sql.lisp: Fix TRUNCATE command, bug reported + by Marcus Pearce + * sql/sql.lisp: Remove EXPLAIN function. Postgresql/Oracle + specific and easy for an application to directly support. + Idea from Marcus Pearce. + * base/basic-sql.lisp: Remove DESCRIBE-TABLE top-level + API as duplicates LIST-ATTRIBUTE-TYPES [Marcus Pearce]. + Keep low-level interface for future optimization + supporting LIST-ATTRIBUTE-TYPES command. + * Makefile: Add to db-sqlite and test directories. + Include them in top-level Makefile + +12 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.5 + * sql/relations.lisp: Add missing file + * utils/time.lisp: Fixes/extensions [Marcus Pearce] + * test/test-time.lips: New file [Marcus Pearce] + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.4 + * test/test-init.lisp: Properly handle object + creation. Close database after use. + * sql/sql.lisp: Make DESCRIBE-TABLE a generic + function so can have methods specialized on + table being a string or an sql-table object. + * base/pool.lisp: Really fix CMUCL locking + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.3 + * test/test-init.lisp: Signal correctly + if any errors occurred in any test suite + * base/loop-extensions.lisp: Fix error + introduced for Lispworks + * base/pool.lisp: Fix locking for CMUCL + * base/objects.lisp: Remove schema-version cruft + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.2: New CLSQL API functions: + DESCRIBE-TABLE AND TRUNCATE-DATABASE + Currently, this are only supported on :postgresql + and :postgresql-socket + * base/database.lisp: automatically load ASDF system + in CONNECT if not already loaded + * base/tests.lisp: disconnect database after testing + * base/*.lisp: Remove CLOSED-DATABASE type in favor + of storing open/closed status in slot of database + * base/pool.lisp: Support locks for CMUCL, OpenMCL, SBCL + * db-postgresql/postgresql-sql.lisp: add DATABASE-RECONNECT, + DATABASE-DESCRIBE-TABLE + * db-sqlite/sqlite-sql.lisp: Add missing slots in database + * base/conditions: Remove duplicate condition + * db-*/*-sql.lisp: Fill new database slot DATABASE-TYPE + * base/recording.lisp: Add new :QUERY type for recording + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.1: documentation fixes, merged + classic-tests into tests + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.6.0 released: New API functions + CREATE-DATABASE, DESTORY-DATABASE, PROBE-DATABASE + * doc/ref_clsql.xml: Document new functions + * base/database.lisp: New API functions + * base/conditions.lisp: Added CLSQL-ACCESS-ERROR + * base/utils.lisp: Fix use of position-char. + Add COMMAND-OUTPUT used by backends for running + external programs. Fix parsing of SQL*NET-compatible + connection-specs. + * base/loop-extension.lisp: Simplify package use + for Lispworks and Allegro + * db-*/*-sql.lisp: Added DATABASE-CREATE, + DATABASE-DESTORY, PROBE-DATABASE methods + * tests/test-init.lisp, clasic-tests/tests.lisp: + Use destroy-database and create-database to ensure + testing with empty database + * tests/test-connection.lisp: Add tests for + parsing of string connection-specs + * examples/run-tests.sh: New file for running + test suite on all installed CL implementations + * examples/clsql-tutorial.lisp: moved from + doc directory + * examples/dot.clsql-tests.config: New file + giving an example test configuration + * test/README: Add notes about rtest/ptester + downloads and link to sample test configuration file. + +10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.5.1 released: + * tests/*.lisp: Rework so tests are run + on multiple backends automatically based + on the contents of ~/.clsql-tests.config. + Reuse helper functions from classic-tests. + * base/database.lisp: Support connection-spec + as string for CONNECT + * classic-tests/tests.lisp: Automatically + load database backends as needed. + +09 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.5.0 released: + All tests for CLSQL and CLSQL-CLASSIC pass + on all platforms. + * base/loop-extension.lisp: Add Lispworks + loop-extension. Improve type specifying on + other platforms. + +09 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.4.2 released: + loop extension now supported on Allegro, all + CLSQL-TESTS pass on Allegro. + * sql/metaclasses.lisp: Some optimization + of compute-slots, be selective when + ordered-class-slots needs to be called + instead of class-slots + * TODO: add URL with documentation on + extending Lispworks LOOP form + +09 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.4.1 released: CLSQL-TESt suite passes + all tests for postgresql and CMUCL, SBCL, OpenMCL. + Allegro and Lispworks pass all tests except for + FDML/LOOP/1 since the loop extension have not yet + been ported to those implementions. + * sql/metaclasses.lisp: Added new slot to standard-db-class + to hold user-specified type. OpenMCL adjustments to compensate + for its type-predicate function. Since AllegroCL, Lispworks, + and OpenMCL have different slot orders, added compute-slots + and ordered-class-slots functions so their slot order matches + SBCL/CMUCL. + +08 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.4.0 released: All tests for clsql-classic now finish + correctly on Allegro, Lispworks, CMUCL, SBCL, OpenMCL for + mysql, postgresql, postgresql-sockets, and sqlite backends. + * db-mysql/mysql-sql.lisp: Fix array dereferencing + * classic-tests/tests.lisp: Fix package name of + number-to-sql-string. + * clsql.asd/clsql-tests.asd: Add support for asdf:test-op + * db-sqlite/sqlite-api-{uffi,sql}.lisp: Multiple UFFI fixes, + now passes tests on all support UFFI platforms. + * db-postgresql-socket/postgresql-socket-api.list: Ported to + SBCL and OpenMCL + * multiple: Finish renaming of :types keyword to :result-types for + greater CommonSQL compatibility, including documentation + * sql/basic-cmds.lisp: Remove obsolete file + +08 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Version 2.3.3 released + * Fixes for sequences on mysql and sqlite [Marcus Pearce] + * Fixes for uffi sqlite backend [Aurelio Bignoli / Kevin Rosenberg] + * Fix for schema table [Marcus Pearce] + * Add loop extension support for SBCL and OpenMCL [Marcus Pearce] + * Fixes to test suite [Marcus Pearce] + +06 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * db-*/*-sql.lisp: Ensure that expr in + database-query-result-set is a string + * Documentation integration + +06 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * With for Marcus Pearce's excellent work, I've merged + his clsql-usql port into clsql. The original clsql + interface is available in the clsql-classic package. + +02 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Integrate patch from Marcus Pearce + adding further support for providing backend for UncommonSQL + +10 Mar 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Integrate patch from Aurelio Bignoli for SQLite backend + +11 Nov 2003 Kevin Rosenberg (kevin@rosenberg.net) + * Converted documentation to XML format + * Made package installable with asdf-install + +23 Jul 2003 Kevin Rosenberg (kevin@rosenberg.net) + * Add for-each-row macro + +12 Dec 2002 Kevin Rosenberg (kevin@rosenberg.net) + * uffi/clsql-uffi.lisp: return NIL for numeric fields that are NULL + +16 Oct 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Add support for SBCL, OpenMCL, and SCL + * Add *load-truename* to search path for clsql's + compiled libraries. + +01 Sep 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Rework use of file types in .asd files + +17 Aug 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Add .asd definition files for ASDF users + +31 Jul 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Restructure directories for Common Lisp Controller v3 compatibility + +25 Jul 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Also change case of logical host in loader files + * Rework handling of logical pathnames + +05 Jul 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Change case of logical host + +14 May 2002 Kevin Rosenberg (kevin@rosenberg.net) + * clsql-base.system: Added base package that can be used without + high-level SQL commands. Used for adding support for UncommonSQL. + * *.system: Reworked logical pathnames to be more consistent with + Common Lisp Controller. + * debian/*: Completed initial Debian support + +10 May 2002 Marc Battyani (marc.battyani@fractalconcept.com) + * sql/classes.cl: + * sql/transactions.cl: + Added transaction support. Functions/macros added: + with-transaction, commit-transaction, rollback-transaction, + add-transaction-commit-hook, add-transaction-rollback-hook + +04 May 2002 Marc Battyani (marc.battyani@fractalconcept.com) + * sql/sql.cl: + * sql/pool.cl: + * sql/functional.cl: + Added pool support in connect/disconnect and with-database. + Removed with-db-from-pool as with-database can now works with the connections pool + +01 May 2002 Marc Battyani (marc.battyani@fractalconcept.com) + * sql/sql.cl: + * sql/pool.cl: + * sql/classes.cl: + * sql/package.cl: + Completed connection pool. + Added with-db-from-pool macro. + +27 Apr 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Multiple files: + Added initial support for connection pool + * sql/transactions.cl + Took transaction code from UncommonSQL and integrated + into CLSQL code. See file for disclaimer about why this + was added. + +23 Apr 2002 Kevin Rosenberg (kevin@rosenberg.net) + * interfaces/postgresql/postgresql-sql.cl: + Fix keyword typo in database-read-large-object + * interfaces/mysql/mysql-loader.cl + Fix loading on Win32 + * test-suite/tester-clsql.cl + Fix type coercion of double-float + * doc/* + Added debian docbook catalog, made it the default + +19 Apr 2002 Marc Battyani (marc.battyani@fractalconcept.com) + * interface/postgresql/postgresql-api.cl: + * interface/postgresql/postgresql-sql.cl: + * sql/sql.cl: + * sql/db-interface.cl: + Added large objects support for postgresql. + +07 Apr 2002 Kevin Rosenberg (kevin@rosenberg.net) + * src/postgresql-socket/postgresql-socket-api.cl: + Fixed find-foreign-function call, eliminated crypt warning + * Makefiles: + Multiple improvements + * sql/usql.cl: + Moved functionality from low-level interfaces to this file + via generic functions + * test-suite/tester.cl: + Added test with acl-compat-tester, moved others to old-tests + directory. + +06 Apr 2002 Kevin Rosenberg (kevin@rosenberg.net) + * src/usql.cl: + Reinstated commented out sections + * interfaces/postgresql/postgresql-loader.cl: + * interfaces/mysql/mysql-loader.cl: + Updated find-forieign-library support. + * interfaces/postgresql-socket/postgresql-socket-package.cl: + Fixed require form for Lispworks (Thanks Marc Battyani!) + * interfaces/postgresql-socket/postgresql-socket-api.cl: + Fixed eval of def-function for crypt library. + +31 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Added interface to support USQL high-level rouines + +29 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Separated db-interface and conditions from sql/sql.cl + * Improved foreign library loading testing + * interfaces/postgresql/postgresql-api.cl + Added PQisBusy function + * interfaces/clsql-uffi/clsql-uffi.cl + Fixed sign error for 64-bit processing + +27 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * interfaces/postgresql-socket/postgresql-socket-api.cl: + Fixes to read-double-from-socket. Added 64-bit integer support. + * test-suite/xptest-clsql.cl + Added testint for 64-bit integers + * Additons to installation docs + +26 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * interfaces/postgresql-socket/postgresql-socket-api.cl: + Implemented direct socket reading for field type :double + * Added usage information for :types to documentation + * interfaces/mysql/mysql-sql.cl: Fixed type specifiers in atoi, + atol, atof calls + * interfaces/clsql-uffi: Created new directory. Split common + interface routines that use UFFI into this package. Required + especially to support direct reading of 64-bit integers into + bignums and bypassing temporary strings. + * test-clsql.cl: Updated to test postgresql-socket's + read-double-from-socket function. + * test-suite/xptest-clsql.cl + Started work on test suite + +25 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * interfaces/mysql/mysql-api.cl: Added mysql-fetch-fields, + mysql-fetch-field-direct Got :auto types working + * interfaces/postgresql/postgresql-api.cl + * interfaces/postgresql-socket/postgresql-socket-api.cl + Added pgsql-field-types enum. Got :auto types working. + * multiple-files + Renamed :field-types to :types. + +24 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * Added field-types parameter to query, database-query, + database-query-result-set, map-query. Haven't added code + to utilize field types, yet. + * Changed postgresql-socket result set from cons to a structure + * Updated test-clsql.cl to use automated testing with a config + file + * Changed return types of field accessors from cstring to + (* :unsigned-char). This prepares for being able to use specified + type conversions when taking field data into lisp. + * Added field-type processing for most interfaces. Not done yet. + +23 Mar 2002 Kevin Rosenberg (kevin@rosenberg.net) + * doc/ref.sgml: Updated MAP-QUERY example to use + *read-default-float-format* (John Foderaro) + * Extensive work to foreign library loaders and .system files to + check for successful loading of foreign libraries. + * Modified test-clsql.cl to allow more modularity and + automated testing in future release. + * mysql/mysql-sql.lisp: Added field types + +01 Jan 2002 Kevin Rosenberg (kevin@rosenberg.net) + * mysql/mysql-sql.lisp: + - Added support for Allegro CL and Lispworks using UFFI layer + - Changed database-connect to use mysql-real-connect. This way, + can avoid using double (unwind-protect) + - Changed database-connect to have MySQL library allocate space + for MYSQL structure. This will make the code more robust in + the event that MySQL library changes the size of the mysql-mysql + structure. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..bd96022 --- /dev/null +++ b/INSTALL @@ -0,0 +1,5 @@ +Refer to the main documentation file for installation instructions. + +Documentation is availabe as a PDF file in doc/clsql.pdf and as HTML +files in doc/html.tar.gz. + diff --git a/LATEST-TEST-RESULTS b/LATEST-TEST-RESULTS new file mode 100644 index 0000000..72725ce --- /dev/null +++ b/LATEST-TEST-RESULTS @@ -0,0 +1,81 @@ +Note from Russ Tyndall 2015-03-23 : + +This is the current results of running the test suite against all the database +backends I have accessible, on SBCL / UBUNTU64bit. It would be great to +continue improving the test suite and skip tests that reliably fail, improve +tests so that all pass. In the interim, I would like know that I am not +increasing the number of failing tests + +:mysql +No tests failed. +18 of 310 Tests skipped + +:odbc MSSQL2000/5 +No tests failed. +22 of 306 Tests skipped: + +:odbc postgres +*couldnt get them to run - foreign lib problems* + +:postgres-socket :postgres-socket-3 +4 out of 308 total tests failed: :TIME/PG/OODML/USEC, :TIME/PG/OODML/NO-USEC, + :TIME/PG/FDML/USEC, :FDML/SELECT/36. +20 of 308 Tests skipped: + +:sqlite3 +1 out of 308 total tests failed: :FDDL/INDEX/3. +20 of 308 Tests skipped: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +Version 3.0.0 run on August 3, 2004 on x86, x86_64, and PowerPC platforms + +POSTGRESQL: All 233 tests passed (x86, Allegro CL Enterprise Edition). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (x86, Allegro CL Enterprise Edition). +MYSQL: All 213 tests passed (x86, Allegro CL Enterprise Edition). +SQLITE: All 224 tests passed (x86, Allegro CL Enterprise Edition). +ODBC/POSTGRESQL: All 232 tests passed (x86, Allegro CL Enterprise Edition). +ODBC/MYSQL: All 213 tests passed (x86, Allegro CL Enterprise Edition). +ORACLE: All 227 tests passed (x86, Allegro CL Enterprise Edition). +AODBC/UNKNOWN: 23 of 231 tests failed (x86, Allegro CL Enterprise Edition). +POSTGRESQL: All 233 tests passed (X86, CMU Common Lisp). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (X86, CMU Common Lisp). +MYSQL: All 213 tests passed (X86, CMU Common Lisp). +SQLITE: All 224 tests passed (X86, CMU Common Lisp). +ODBC/POSTGRESQL: All 232 tests passed (X86, CMU Common Lisp). +ODBC/MYSQL: All 213 tests passed (X86, CMU Common Lisp). +ORACLE: All 227 tests passed (X86, CMU Common Lisp). +POSTGRESQL: All 233 tests passed (NIL, LispWorks). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (NIL, LispWorks). +MYSQL: All 213 tests passed (NIL, LispWorks). +SQLITE: All 224 tests passed (NIL, LispWorks). +ODBC/POSTGRESQL: All 232 tests passed (NIL, LispWorks). +ODBC/MYSQL: All 213 tests passed (NIL, LispWorks). +ORACLE: All 227 tests passed (NIL, LispWorks). +POSTGRESQL: All 233 tests passed (X86, SBCL). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (X86, SBCL). +MYSQL: All 213 tests passed (X86, SBCL). +SQLITE: All 224 tests passed (X86, SBCL). +ODBC/POSTGRESQL: All 232 tests passed (X86, SBCL). +ODBC/MYSQL: All 213 tests passed (X86, SBCL). +ORACLE: All 227 tests passed (X86, SBCL). + +POSTGRESQL: All 233 tests passed (64-bit AMD64, Allegro CL Enterprise Edition). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (64-bit AMD64, Allegro CL Enterprise Edition). +MYSQL: All 223 tests passed (64-bit AMD64, Allegro CL Enterprise Edition). +SQLITE: All 224 tests passed (64-bit AMD64, Allegro CL Enterprise Edition). +ORACLE: All 227 tests passed (64-bit AMD64, Allegro CL Enterprise Edition). + +POSTGRESQL: All 233 tests passed (PowerPC, SBCL). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (PowerPC, SBCL). +MYSQL: All 217 tests passed (PowerPC, SBCL). +SQLITE: All 224 tests passed (PowerPC, SBCL). +ODBC/POSTGRESQL: All 232 tests passed (PowerPC, SBCL). +ODBC/MYSQL: All 217 tests passed (PowerPC, SBCL). +POSTGRESQL: All 233 tests passed (ppc, OpenMCL). +POSTGRESQL-SOCKET/POSTGRESQL: All 233 tests passed (ppc, OpenMCL). +MYSQL: All 217 tests passed (ppc, OpenMCL). +SQLITE: All 224 tests passed (ppc, OpenMCL). +ODBC/POSTGRESQL: All 232 tests passed (ppc, OpenMCL). +ODBC/MYSQL: All 217 tests passed (ppc, OpenMCL). + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dccbe4d --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (C) 2002-2022 by Kevin M. Rosenberg +Copyright (C) 1999-2001 Pierre R. Mai (MaiSQL ) +Copyright (C) 1999-2020 onShore Development (UncommonSQL) +Copyright (C) 1999-2001 Paul Meurer (SQL/ODBC) +Copyright (C) 2010-2015 Marc Battyani + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0565167 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for the CLSQL package +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +PKG := clsql +DEBPKG := cl-sql +SUBDIRS := sql tests uffi db-mysql db-aodbc db-odbc \ + db-postgresql db-postgresql-socket db-sqlite \ + db-oracle +DOCSUBDIRS:=doc + +include Makefile.common + +LIBSUBDIRS=db-mysql uffi +.PHONY: subdirs $(LIBSUBDIRS) + +.PHONY: all +all: $(LIBSUBDIRS) + +$(LIBSUBDIRS): + $(MAKE) -C $@ + +SOURCE_FILES=interfaces sql cmucl-compat doc test-suite Makefile VERSION \ + COPYING.CLSQL COPYING.MaiSQL README INSTALL ChangeLog NEWS TODO \ + set-logical.cl clsql-uffi.system \ + clsql.system clsql-aodbc.system clsql-mysql.system \ + clsql-postgresql.system clsql-postgresql-socket.system + +.PHONY: doc +doc: + $(MAKE) -C doc + +.PHONY:dist +dist: clean + @$(MAKE) -C doc $@ + + diff --git a/Makefile.common b/Makefile.common new file mode 100644 index 0000000..358d75d --- /dev/null +++ b/Makefile.common @@ -0,0 +1,44 @@ +UNAME=$(shell uname) +UNAME_A=$(shell uname -a) +DARWIN_LIBC=$(shell file /usr/lib/libc.dylib) + +OS_AIX=$(shell expr "$(UNAME)" : '.*AIX.*') +OS_SUNOS=$(shell expr "$(UNAME)" : '.*SunOS.*') +OS_DARWIN=$(shell expr "$(UNAME)" : '.*Darwin.*') +ifneq ($(OS_DARWIN),0) + OS_DARWIN64=$(shell expr "$(DARWIN_LIBC)" : '.*x86_64.*') +else + OS_DARWIN64=0 +endif +OS_CYGWIN=$(shell expr "$(UNAME)" : '.*CYGWIN.*') +OS_LINUX=$(shell expr "$(UNAME)" : '.*Linux.*') +OS_LINUX64=$(shell expr "$(UNAME_A)" : '.*Linux.*x86_64.*') +ifneq ("$(wildcard /usr/bin/dpkg-buildflags)","") +DPKG_BUILDFLAGS=1 +else +DPKG_BUILDFLAGS=0 +endif + +all: + + +.PHONY: clean +clean: + @rm -rf .bin + @rm -f *.ufsl *.fsl *.fas *.x86f *.sparcf *.fasl *.pfsl *.dfsl + @rm -f *.fasla8 *.fasla16 *.faslm8 *.faslm16 *.faslmt + @rm -f *~ *.bak *.orig *.err \#*\# .#* + @rm -f *.so *.a + @rm -rf debian/cl-sql +ifneq ($(SUBDIRS)$(DOCSUBDIRS),) + @set -e; for i in $(SUBDIRS) $(DOCSUBDIRS); do \ + $(MAKE) -C $$i $@; done +endif + +distclean: clean +ifneq ($(SUBDIRS)$(DOCSUBDIRS),) + @set -e; for i in $(SUBDIRS) $(DOCSUBDIRS); do \ + $(MAKE) -C $$i $@; done +endif + +.SUFFIXES: # No default suffixes diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..b69ad00 --- /dev/null +++ b/NEWS @@ -0,0 +1,47 @@ +Sep 14 2006 +----------- +Version 4.0.0 release with incompatible backward change: + - using ANSI mode for MySQL backend + - reducing case changes in SQL output for symbols which will have an effect + for users of mlisp (Allegro "Modern" Lisp) . + - Add support for generating quoted strings in SQL output when +specifying table names as strings rather than symbols. This may have +an effect on applications that use strings as table names and use +inconsistent case in the table names. Previously, those varying case +strings would be mapped to a canonical case unquoted SQL output. But +to accomodate users which need to specify case in table names, this +mapping has been removed. +See ChangeLog entry for details. + +Dec 30 2006 +----------- +Version 3.8.0 released with incompatible backward change. See +the ChangeLog entry for details. + + +Aug 3 2006 +---------- +CLSQL 3.0 has been released. The 3.0 release of CLSQL is a major +rewrite compared to version 2.0. New features include: + + - Full CommonSQL backward compatibility with native documentation while + retaining and adding many improvements beyond the CommonSQL + standard. + - Extensive (233 tests) regression suite + - Addition of ODBC and Oracle backends + +I wish to acknowledge Marcus Pearce's significant contribution to this +release. He has performed the initial port of Uncommonsql to CLSQL as +well as the initial regression suite and most of the new +documentation. He has contributed to many of the re-design decisions +and new code for CLSQL. + +CLSQL's home is http://clsql.kpe.io + +Enjoy! + +Kevin Rosenberg + + + + diff --git a/README b/README new file mode 100644 index 0000000..8fdfd89 --- /dev/null +++ b/README @@ -0,0 +1,43 @@ +CLSQL is a Common Lisp to SQL engine interface by Kevin M. +Rosenberg. It includes both functional and object oriented subsystems +for data definition and manipulation as well as an integrated symbolic +SQL syntax. + +CLSQL supports a number of RDBMS and uses the UFFI (http://uffi.kpe.io) +library for compatibility with Allegro CL, Lispworks, CMUCL, SBCL and +OpenMCL. + +CLSQL has incorporated code from the following projects. At this point +in 2004, development of has stopped on these incorporated projects. + - Pierre Mai's MaiSQL + - onShore Development's UncommonSQL + - Paul Meurer's SQL/ODBC + - Cadabra's Oracle interface + +CLSQL's home is http://clsql.kpe.io. + +Documentation is available as a PDF file in doc/clsql.pdf and as HTML +files in doc/html.tar.gz. + + +CONTRIBUTING +------------ + +If you would like to report a bug please do so through the clsql +mailing list. http://lists.kpe.io/mailman/listinfo/CLSQL + +Patches are welcome. It will be much easier for us to incorporate if +you use [git](http://git-scm.com/). Please keep distinct changes in +seperate patches as it makes it much easier to review. If you have +something small to to send to the mailing list `git format-patch` is +your friend. + +If you have a bigger set of patches then I recommend creating a fork +on github from https://github.com/UnwashedMeme/clsql. Once your +patches are available there either issue a pull request or let us know +about it on the mailing list. + +* http://help.github.com/ has some excellent tutorials on getting + started. +* http://git-scm.com/book is an excellent in depth tutorial on how to + use git effectively. diff --git a/TODO b/TODO new file mode 100644 index 0000000..795ec6a --- /dev/null +++ b/TODO @@ -0,0 +1,27 @@ +TESTS TO ADD + +* Test that ":db-kind :key" adds an index for that key. This is complicated by + different backends showing autogenerated primary key in different ways. +* :db-constraint tests +* Number and Char field types +* symbol slot +* generalized-boolean slot +* tests for db-reader and db-writer + +OPTIMIZATIONS + +* Revisit result-type list creation, perhaps caching +* Rework LIST-ATTRIBUTE-TYPES and LIST-INDEXES such that they exhibit their + current behaviours using single database queries. +* Possible use of cached attribute type information for operations which + insert/update records as mentioned in the CommonSQL user guide. +* Extend caching of generated SQL strings to more complex expressions which + are likely to be repeatedly executed many times. + +POSSIBLE EXTENSIONS + +* improve large object api and extend to databases beyond postgresql +* add support for prepared statements + +RACE CONDITIONS +* sql/databases.lisp: *connected-databases* is shared globally but not modified in a threadsafe manner. diff --git a/clsql-aodbc.asd b/clsql-aodbc.asd new file mode 100644 index 0000000..42e4cdb --- /dev/null +++ b/clsql-aodbc.asd @@ -0,0 +1,37 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-aodbc.asd +;;;; Purpose: ASDF definition file for CLSQL AODBC backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-aodbc-system (:use #:asdf #:cl)) +(in-package #:clsql-aodbc-system) + +#+(and allegro (not allegro-cl-trial)) +(defsystem clsql-aodbc + :name "cl-sql-aodbc" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL AODBC Driver" + :long-description "cl-sql-aodbc package provides a database driver to AllegroCL's AODBC database interface." + + :depends-on (clsql) + :components + ((:module :db-aodbc + :components + ((:file "aodbc-package") + (:file "aodbc-sql" :depends-on ("aodbc-package")))))) + +#-(and allegro (not allegro-cl-trial)) +(defsystem clsql-aodbc) diff --git a/clsql-cffi.asd b/clsql-cffi.asd new file mode 100644 index 0000000..fb6fb7d --- /dev/null +++ b/clsql-cffi.asd @@ -0,0 +1,27 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-cffi.asd +;;;; Purpose: ASDF System definition for CLSQL using CFFI-UFFI-COMPAT +;;;; Author: Kevin M. Rosenberg +;;;; Created: Jan 2010 +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(push :clsql-cffi cl:*features*) + +(defpackage #:clsql-cffi-system (:use #:asdf #:cl)) +(in-package #:clsql-cffi-system) + +(defsystem clsql-cffi + :name "CLSQL-CFFI" + :author "Kevin Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "CLSQL using CFFI-UFFI-COMPAT interface" + :depends-on (clsql) + :components nil) diff --git a/clsql-db2.asd b/clsql-db2.asd new file mode 100644 index 0000000..4007017 --- /dev/null +++ b/clsql-db2.asd @@ -0,0 +1,40 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-db2.asd +;;;; Purpose: ASDF definition file for CLSQL Db2 backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-db2-system (:use #:asdf #:cl)) +(in-package #:clsql-db2-system) + +;;; System definition + +(defsystem clsql-db2 + :name "clsql-db2" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL Db2 Driver" + :long-description "cl-sql-db2 package provides a database driver to the Db2 database system." + + :depends-on (clsql-uffi) + :components + ((:module :db-db2 + :components + ((:file "db2-package") + (:file "db2-loader" :depends-on ("db2-package")) + (:file "foreign-resources" :depends-on ("db2-package")) + (:file "db2-constants" :depends-on ("db2-package")) + (:file "db2-api" :depends-on ("db2-constants" "db2-loader")) + (:file "db2-sql" :depends-on ("db2-api" "foreign-resources")) + (:file "db2-objects" :depends-on ("db2-sql")))))) diff --git a/clsql-mysql.asd b/clsql-mysql.asd new file mode 100644 index 0000000..12b8479 --- /dev/null +++ b/clsql-mysql.asd @@ -0,0 +1,92 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-mysql.asd +;;;; Purpose: ASDF definition file for CLSQL MySQL backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-mysql-system (:use #:asdf #:cl)) +(in-package #:clsql-mysql-system) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (unless (find-package 'uffi) + (asdf:operate 'asdf:load-op 'uffi))) + +(defvar *library-file-dir* + (merge-pathnames "db-mysql/" + (make-pathname :name nil :type nil + :defaults *load-truename*))) + +(defclass clsql-mysql-source-file (c-source-file) + ()) + +(defmethod output-files ((o compile-op) (c clsql-mysql-source-file)) + (let* ((library-file-type + (funcall (intern (symbol-name'#:default-foreign-library-type) + (symbol-name '#:uffi)))) + (found (some #'(lambda (dir) + (probe-file (make-pathname :directory dir + :name (component-name c) + :type library-file-type))) + '((:absolute "usr" "lib" "clsql"))))) + (list (if found + found + (make-pathname :name (component-name c) + :type library-file-type + :defaults *library-file-dir*))))) + +(defmethod perform ((o load-op) (c clsql-mysql-source-file)) + t) + +(defmethod operation-done-p ((o load-op) (c clsql-mysql-source-file)) + (and (find-package '#:mysql) + (symbol-function (intern (symbol-name '#:mysql-get-client-info) + (find-package '#:mysql))) + t)) + +(defmethod perform ((o compile-op) (c clsql-mysql-source-file)) + (unless (operation-done-p o c) + #-(or win32 win64 windows mswindows) + (unless (zerop (run-shell-command + #-freebsd "cd ~A; make" + #+freebsd "cd ~A; gmake" + (namestring *library-file-dir*))) + (error 'operation-error :component c :operation o)))) + +(defmethod operation-done-p ((o compile-op) (c clsql-mysql-source-file)) + (or (and (probe-file #p"/usr/lib/clsql/clsql_mysql.so") t) + (let ((lib (make-pathname :defaults (component-pathname c) + :type (uffi:default-foreign-library-type)))) + (and (probe-file lib) (probe-file (component-pathname c)) + (> (file-write-date lib) (file-write-date (component-pathname c))))))) + +;;; System definition + +(defsystem :clsql-mysql + :name "cl-sql-mysql" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL MySQL Driver" + :long-description "cl-sql-mysql package provides a database driver to the MySQL database system." + + :depends-on (clsql clsql-uffi) + :components + ((:module :db-mysql + :components + ((:file "mysql-package") + (:clsql-mysql-source-file "clsql_mysql" :depends-on ("mysql-package")) + (:file "mysql-loader" :depends-on ("mysql-package" "clsql_mysql")) + (:file "mysql-client-info" :depends-on ("mysql-loader")) + (:file "mysql-api" :depends-on ("mysql-client-info")) + (:file "mysql-sql" :depends-on ("mysql-api")) + (:file "mysql-objects" :depends-on ("mysql-sql")))))) diff --git a/clsql-odbc.asd b/clsql-odbc.asd new file mode 100644 index 0000000..30c04cf --- /dev/null +++ b/clsql-odbc.asd @@ -0,0 +1,39 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-odbc.asd +;;;; Purpose: ASDF definition file for CLSQL ODBC backend +;;;; Author: Kevin M. Rosenberg +;;;; Created: April 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 200d42 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-odbc-system (:use #:asdf #:cl)) +(in-package #:clsql-odbc-system) + +(defsystem clsql-odbc + :name "clsql-odbc" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL ODBC Driver" + :long-description "cl-sql-odbc package provides a database driver to the ODBC database system." + + :depends-on (clsql clsql-uffi) + :components + ((:module :db-odbc + :components + ((:file "odbc-package") + (:file "odbc-loader" :depends-on ("odbc-package")) + (:file "odbc-constants" :depends-on ("odbc-loader")) + (:file "odbc-ff-interface" :depends-on ("odbc-constants")) + (:file "odbc-api" :depends-on ("odbc-ff-interface" "odbc-constants")) + (:file "odbc-dbi" :depends-on ("odbc-api")) + (:file "odbc-sql" :depends-on ("odbc-dbi")))))) + diff --git a/clsql-oracle.asd b/clsql-oracle.asd new file mode 100644 index 0000000..62d9eb7 --- /dev/null +++ b/clsql-oracle.asd @@ -0,0 +1,40 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-oracle.asd +;;;; Purpose: ASDF definition file for CLSQL Oracle backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-oracle-system (:use #:asdf #:cl)) +(in-package #:clsql-oracle-system) + +;;; System definition + +(defsystem clsql-oracle + :name "clsql-oracle" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL Oracle Driver" + :long-description "cl-sql-oracle package provides a database driver to the Oracle database system." + + :depends-on (clsql-uffi) + :components + ((:module :db-oracle + :components + ((:file "oracle-package") + (:file "oracle-loader" :depends-on ("oracle-package")) + (:file "foreign-resources" :depends-on ("oracle-package")) + (:file "oracle-constants" :depends-on ("oracle-package")) + (:file "oracle-api" :depends-on ("oracle-constants" "oracle-loader")) + (:file "oracle-sql" :depends-on ("oracle-api" "foreign-resources")) + (:file "oracle-objects" :depends-on ("oracle-sql")))))) diff --git a/clsql-postgresql-socket.asd b/clsql-postgresql-socket.asd new file mode 100644 index 0000000..2051b7a --- /dev/null +++ b/clsql-postgresql-socket.asd @@ -0,0 +1,38 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-postgresql-socket.asd +;;;; Purpose: ASDF file for CLSQL PostgresSQL socket backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-postgresql-socket-system (:use #:asdf #:cl)) +(in-package #:clsql-postgresql-socket-system) + +;;; System definition + +(defsystem clsql-postgresql-socket + :name "cl-sql-postgresql-socket" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL PostgreSQL Socket Driver" + :long-description "cl-sql-postgresql-socket package provides a database driver to the PostgreSQL database via a socket interface." + + :depends-on (clsql md5 #-:clsql-cffi uffi #+:clsql-cffi cffi-uffi-compat #+sbcl sb-bsd-sockets) + :components + ((:module :db-postgresql-socket + :components + ((:file "postgresql-socket-package") + (:file "postgresql-socket-api" + :depends-on ("postgresql-socket-package")) + (:file "postgresql-socket-sql" + :depends-on ("postgresql-socket-api")))))) diff --git a/clsql-postgresql-socket3.asd b/clsql-postgresql-socket3.asd new file mode 100644 index 0000000..cd6a6d8 --- /dev/null +++ b/clsql-postgresql-socket3.asd @@ -0,0 +1,41 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-postgresql-socket.asd +;;;; Purpose: ASDF file for CLSQL PostgresSQL socket (protocol vs 3) backend +;;;; Programmer: Russ Tyndall +;;;; Date Started: Sept 2009 +;;;; +;;;; $Id$ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-postgresql-socket-system (:use #:asdf #:cl)) +(in-package #:clsql-postgresql-socket-system) + +;;; System definition + +(defsystem clsql-postgresql-socket3 + :name "cl-sql-postgresql-socket3" + :author "Russ Tyndall " + :maintainer "Russ Tyndall " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL PostgreSQL Socket Driver" + :long-description "cl-sql-postgresql-socket package provides a database driver to the PostgreSQL database via a socket interface." + + :depends-on (clsql + md5 + :cl-postgres + (:feature sbcl sb-bsd-sockets)) + :components + ((:module :db-postgresql-socket3 + :serial T + :components ((:file "package") + (:file "api") + (:file "sql"))))) diff --git a/clsql-postgresql.asd b/clsql-postgresql.asd new file mode 100644 index 0000000..5c73bba --- /dev/null +++ b/clsql-postgresql.asd @@ -0,0 +1,37 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-postgresql.asd +;;;; Purpose: ASDF file for CLSQL PostgresSQL backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-postgresql-system (:use #:asdf #:cl)) +(in-package #:clsql-postgresql-system) + +#+(and allegro macosx) (push "so" excl::*load-foreign-types*) + +(defsystem clsql-postgresql + :name "cl-sql-postgresql" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp PostgreSQL API Driver" + :long-description "cl-sql-postgresql package provides a the database driver for the PostgreSQL API." + + :depends-on (clsql clsql-uffi) + :components + ((:module :db-postgresql + :components + ((:file "postgresql-package") + (:file "postgresql-loader" :depends-on ("postgresql-package")) + (:file "postgresql-api" :depends-on ("postgresql-loader")) + (:file "postgresql-sql" :depends-on ("postgresql-api")))))) diff --git a/clsql-sqlite.asd b/clsql-sqlite.asd new file mode 100644 index 0000000..a8538dc --- /dev/null +++ b/clsql-sqlite.asd @@ -0,0 +1,36 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-sqlite.asd +;;;; Purpose: ASDF file for CLSQL SQLite backend +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-sqlite-system (:use #:asdf #:cl)) +(in-package #:clsql-sqlite-system) + +(defsystem clsql-sqlite + :name "cl-sql-sqlite" + :author "Aurelio Bignoli " + :maintainer "Aurelio Bignoli" + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQLite Driver" + :long-description "cl-sql-sqlite package provides a database driver to SQLite database library." + + + :depends-on (clsql clsql-uffi) + :components + ((:module :db-sqlite + :components + ((:file "sqlite-package") + (:file "sqlite-loader" :depends-on ("sqlite-package")) + (:file "sqlite-api" :depends-on ("sqlite-loader")) + (:file "sqlite-sql" :depends-on ("sqlite-api")))))) diff --git a/clsql-sqlite3.asd b/clsql-sqlite3.asd new file mode 100644 index 0000000..f83d2dd --- /dev/null +++ b/clsql-sqlite3.asd @@ -0,0 +1,37 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-sqlite3.asd +;;;; Purpose: ASDF file for CLSQL SQLite3 backend +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-sqlite3-system (:use #:asdf #:cl)) +(in-package #:clsql-sqlite3-system) + +(defsystem clsql-sqlite3 + :name "cl-sql-sqlite3" + :author "Aurelio Bignoli " + :maintainer "Aurelio Bignoli" + :licence "Lessor Lisp General Public License" + :description "Common Lisp Sqlite3 Driver" + :long-description "cl-sql-sqlite3 package provides a database driver to SQLite Versione 3 database library." + + + :depends-on (clsql clsql-uffi) + :components + ((:module :db-sqlite3 + :components + ((:file "sqlite3-package") + (:file "sqlite3-loader" :depends-on ("sqlite3-package")) + (:file "sqlite3-api" :depends-on ("sqlite3-loader")) + (:file "sqlite3-sql" :depends-on ("sqlite3-api")) + (:file "sqlite3-methods" :depends-on ("sqlite3-sql")))))) diff --git a/clsql-tests.asd b/clsql-tests.asd new file mode 100644 index 0000000..f0280fc --- /dev/null +++ b/clsql-tests.asd @@ -0,0 +1,60 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; File: clsql-tests.asd +;;;; Authors: Marcus Pearce and Kevin Rosenberg +;;;; Created: 30/03/2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) +(defpackage #:clsql-tests-system (:use #:asdf #:cl)) +(in-package #:clsql-tests-system) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (unless (find-package '#:uffi) + (asdf:operate 'asdf:load-op 'uffi))) + +(defsystem clsql-tests + :name "CLSQL Tests" + :author "" + :maintainer "" + :version "" + :licence "" + :description "A regression test suite for CLSQL." + :depends-on (clsql rt) + :components + ((:module tests + :serial t + :components ((:file "package") + (:file "utils") + (:file "test-init") + (:file "datasets") + (:file "ds-employees") + (:file "ds-nodes") + (:file "ds-artists") + (:file "benchmarks") + (:file "test-internal") + (:file "test-basic") + (:file "test-time") + (:file "test-connection") + (:file "test-fddl") + (:file "test-fdml") + (:file "test-ooddl") + (:file "test-oodml") + (:file "test-syntax") + (:file "test-pool") + ; #-uffi:no-i18n (:file "test-i18n") + )))) + +(defmethod perform ((o test-op) (c (eql (find-system 'clsql-tests)))) + (operate 'load-op 'clsql) + (unless (funcall (intern (symbol-name '#:run-tests) + (find-package '#:clsql-tests))) + (error "test-op failed"))) diff --git a/clsql-uffi.asd b/clsql-uffi.asd new file mode 100644 index 0000000..90e77de --- /dev/null +++ b/clsql-uffi.asd @@ -0,0 +1,38 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-uffi.asd +;;;; Purpose: ASDF definition file for CLSQL UFFI Helper package +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Aug 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package cl-user) + +(defpackage clsql-uffi-system (:use #:asdf #:cl)) +(in-package clsql-uffi-system) + +(defsystem clsql-uffi + :name "cl-sql-base" + :author "Kevin M. Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common UFFI Helper functions for Common Lisp SQL Interface Library" + :long-description "cl-sql-uffi package provides common helper functions using the UFFI for the CLSQL package." + + :depends-on (clsql #-:clsql-cffi (:version uffi "2.0") + #+:clsql-cffi cffi-uffi-compat) + + :components + ((:module :uffi + :components + ((:file "clsql-uffi-package") + (:file "clsql-uffi-loader" :depends-on ("clsql-uffi-package")) + (:file "clsql-uffi" :depends-on ("clsql-uffi-package")))))) diff --git a/clsql.asd b/clsql.asd new file mode 100644 index 0000000..37263ce --- /dev/null +++ b/clsql.asd @@ -0,0 +1,110 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql.asd +;;;; Purpose: ASDF System definition for CLSQL +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) +(defpackage #:clsql-system (:use #:asdf #:cl)) +(in-package #:clsql-system) + +#+(and clisp (not :clsql-cffi)) +(asdf:operate 'asdf:load-op 'clsql-cffi) + +;; need to load uffi for below perform :after method +(eval-when (:compile-toplevel :load-toplevel :execute) + #+:clsql-cffi + (unless (find-package 'cffi-uffi-compat) + #+quicklisp + (ql:quickload :cffi-uffi-compat) + #-quicklisp + (asdf:operate 'asdf:load-op 'cffi-uffi-compat)) + #-:clsql-cffi + (unless (find-package 'uffi) + (asdf:operate 'asdf:load-op 'uffi))) + +(defsystem clsql + :name "CLSQL" + :author "Kevin Rosenberg " + :maintainer "Kevin M. Rosenberg " + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQL Interface library" + :long-description "A Common Lisp interface to SQL RDBMS based on +the Xanalys CommonSQL interface for Lispworks. It provides low-level +database interfaces as well as a functional and an object +oriented interface." + :version "6.4" + :components + ((:module sql + :components + ((:module base + :pathname "" + :components + ((:file "cmucl-compat") + (:file "package") + (:file "kmr-mop" :depends-on ("package")) + (:file "base-classes" :depends-on ("package")) + (:file "conditions" :depends-on ("base-classes")) + (:file "db-interface" :depends-on ("conditions")) + (:file "decimals" :depends-on ("package" "db-interface")) + (:file "utils" :depends-on ("package" "db-interface")) + (:file "time" :depends-on ("package" "conditions" "utils")) + (:file "generics" :depends-on ("package")))) + (:module database + :pathname "" + :components + ((:file "initialize") + (:file "database" :depends-on ("initialize")) + (:file "recording" :depends-on ("database")) + (:file "pool" :depends-on ("database"))) + :depends-on (base)) + (:module syntax + :pathname "" + :components ((:file "expressions") + (:file "operations" + :depends-on ("expressions")) + (:file "syntax" :depends-on ("operations"))) + :depends-on (database)) + (:module functional + :pathname "" + :components ((:file "fdml") + (:file "transaction" :depends-on ("fdml")) + #+clisp (:file "ansi-loop") + (:file "loop-extension" + :depends-on ("fdml" #+clisp "ansi-loop")) + (:file "fddl" :depends-on ("fdml"))) + :depends-on (syntax)) + (:module object + :pathname "" + :components ((:file "metaclasses") + (:file "ooddl" :depends-on ("metaclasses")) + (:file "oodml" :depends-on ("ooddl"))) + :depends-on (functional)) + (:module generic + :pathname "" + :components ((:file "generic-postgresql") + (:file "generic-odbc") + (:file "sequences") + (:file "command-object")) + :depends-on (functional))))) + :in-order-to ((test-op (test-op "clsql-tests")))) + +(defmethod perform :after ((o load-op) (c (eql (find-system 'clsql)))) + (let* ((init-var (uffi::getenv "CLSQLINIT")) + (init-file (or (when init-var (probe-file init-var)) + (probe-file + (concatenate 'string + (namestring (user-homedir-pathname)) + ".clsql-init.lisp")) + (probe-file "/etc/clsql-init.lisp") + #+(or mswin windows win32 win64 mswindows) + (probe-file "c:\\etc\\clsql-init.lisp")))) + (when init-file (load init-file)))) diff --git a/db-aodbc/Makefile b/db-aodbc/Makefile new file mode 100644 index 0000000..31dc910 --- /dev/null +++ b/db-aodbc/Makefile @@ -0,0 +1,6 @@ +SUBDIRS := + +include ../Makefile.common + +.PHONY: distclean +distclean: clean diff --git a/db-aodbc/aodbc-package.lisp b/db-aodbc/aodbc-package.lisp new file mode 100644 index 0000000..40d08f5 --- /dev/null +++ b/db-aodbc/aodbc-package.lisp @@ -0,0 +1,28 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: aodbc-package.cl +;;;; Purpose: Package definition for CLSQL AODBC backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +#+allegro +(eval-when (:compile-toplevel :load-toplevel :execute) + (require :aodbc-v2)) +#-allegro (warn "This system requires Allegro's AODBC library to operate") + +(defpackage #:clsql-aodbc + (:nicknames #:aodbc) + (:use #:common-lisp #:clsql-sys) + (:export #:aodbc-database) + (:documentation "This is the CLSQL interface to Allegro's AODBC")) diff --git a/db-aodbc/aodbc-sql.lisp b/db-aodbc/aodbc-sql.lisp new file mode 100644 index 0000000..98f9ec8 --- /dev/null +++ b/db-aodbc/aodbc-sql.lisp @@ -0,0 +1,102 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: aodbc-sql.cl +;;;; Purpose: Low-level interface for CLSQL AODBC backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-aodbc) + +;; interface foreign library loading routines +(defmethod clsql-sys:database-type-library-loaded ((database-type (eql :aodbc))) + "T if foreign library was able to be loaded successfully. " + (when (find-package :dbi) ;; finds Allegro's DBI (AODBC) package + t)) + +(defmethod clsql-sys:database-type-load-foreign ((databae-type (eql :aodbc))) + t) + +(when (find-package :dbi) + (clsql-sys:database-type-load-foreign :aodbc)) + + +;; AODBC interface + +(defclass aodbc-database (generic-odbc-database) + ((aodbc-db-type :accessor database-aodbc-db-type :initform :unknown))) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :aodbc))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (dsn user password) connection-spec + (declare (ignore password)) + (concatenate 'string dsn "/" user))) + +(defmethod database-connect (connection-spec (database-type (eql :aodbc))) + (check-connection-spec connection-spec database-type (dsn user password)) + #+aodbc-v2 + (destructuring-bind (dsn user password) connection-spec + (handler-case + (make-instance 'aodbc-database + :name (database-name-from-spec connection-spec :aodbc) + :database-type :aodbc + :dbi-package (find-package '#:dbi) + :odbc-conn + (dbi:connect :user user + :password password + :data-source-name dsn)) + (sql-error (e) + (error e)) + (error () ;; Init or Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :message "Connection failed"))))) + + +(defmethod database-query (query-expression (database aodbc-database) + result-types field-names) + #+aodbc-v2 + (handler-case + (dbi:sql query-expression + :db (clsql-sys::odbc-conn database) + :types result-types + :column-names field-names) + #+ignore + (error () + (error 'sql-database-data-error + :database database + :expression query-expression + :message "Query failed")))) + +(defmethod database-create (connection-spec (type (eql :aodbc))) + (warn "Not implemented.")) + +(defmethod database-destroy (connection-spec (type (eql :aodbc))) + (warn "Not implemented.")) + +(defmethod database-probe (connection-spec (type (eql :aodbc))) + (warn "Not implemented.")) + +;;; Backend capabilities + +(defmethod database-underlying-type ((database aodbc-database)) + (database-aodbc-db-type database)) + +(defmethod db-backend-has-create/destroy-db? ((db-type (eql :aodbc))) + nil) + +(defmethod database-initialize-database-type ((database-type (eql :aodbc))) + t) + +(when (clsql-sys:database-type-library-loaded :aodbc) + (clsql-sys:initialize-database-type :database-type :aodbc)) diff --git a/db-db2/Makefile b/db-db2/Makefile new file mode 100644 index 0000000..e6f2448 --- /dev/null +++ b/db-db2/Makefile @@ -0,0 +1,23 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL Db2 interface +# Author: Kevin M. Rosenberg +# Created: May 2004 +# +# This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean diff --git a/db-db2/db2-api.lisp b/db-db2/db2-api.lisp new file mode 100644 index 0000000..42d2609 --- /dev/null +++ b/db-db2/db2-api.lisp @@ -0,0 +1,110 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2.lisp +;;;; Purpose: Package definition for CLSQL Db2 interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + + +;; +;; Opaque pointer types +;; + +(uffi:def-foreign-type cli-handle :pointer-void) +(uffi:def-foreign-type cli-pointer :pointer-void) +(uffi:def-foreign-type cli-char :byte) +(uffi:def-foreign-type cli-ulen :unsigned-int) +(uffi:def-foreign-type cli-len :int) +(uffi:def-foreign-type cli-smallint :short) +(uffi:def-foreign-type cli-usmallint :unsigned-short) + + +(defvar +null-void-pointer+ (uffi:make-null-pointer :void)) +(defvar +null-void-pointer-pointer+ (uffi:make-null-pointer :pointer-void)) + +;;; Check an CLI return code for erroricity and signal a reasonably +;;; informative condition if so. +;;; +;;; ERRHP provides an error handle which can be used to find +;;; subconditions; if it's not provided, subcodes won't be checked. +;;; +;;; NULLS-OK says that a NULL-VALUE-RETURNED subcondition condition is +;;; normal and needn't cause any signal. An error handle is required +;;; to detect this subcondition, so it doesn't make sense to set ERRHP +;;; unless NULLS-OK is set. + +(defmacro def-cli-routine ((c-cli-symbol lisp-cli-fn) c-return &rest c-parms) + (let ((ll (mapcar (lambda (x) (declare (ignore x)) (gensym)) c-parms))) + `(let ((%lisp-cli-fn (uffi:def-function + (,c-cli-symbol ,(intern (concatenate 'string "%" (symbol-name lisp-cli-fn)))) + ,c-parms + :returning ,c-return))) + (defun ,lisp-cli-fn (,@ll &key database nulls-ok) + (let ((result (funcall %lisp-cli-fn ,@ll))) + (case result + (#.SQL_SUCCESS + SQL_SUCCESS) + (#.SQL_SUCCESS_WITH_INFO + (format *standard-output* "sucess with info") + SQL_SUCCESS) + (#.SQL_ERROR + (error 'sql-database-error + :error-id result + :message + (format nil "DB2 error" result))) + (t + (error 'sql-database-error + :message + (format nil "DB2 unknown error, code=~A" result))))))))) + + +(defmacro def-raw-cli-routine + ((c-cli-symbol lisp-cli-fn) c-return &rest c-parms) + (let ((ll (mapcar (lambda (x) (declare (ignore x)) (gensym)) c-parms))) + `(let ((%lisp-cli-fn (uffi:def-function (,c-cli-symbol ,(intern (concatenate 'string "%" (symbol-name lisp-cli-fn)))) + ,c-parms + :returning ,c-return))) + (defun ,lisp-cli-fn (,@ll &key database nulls-ok) + (funcall %lisp-cli-fn ,@ll))))) + + +(def-cli-routine ("SQLAllocHandle" sql-alloc-handle) + :int + (fHandleType cli-smallint) + (hInput cli-handle) + (phOuput (* cli-handle))) + +(def-cli-routine ("SQLConnect" sql-connect) + :int + (hDb cli-handle) + (server :cstring) + (server-len cli-smallint) + (user :cstring) + (user-len cli-smallint) + (password :cstring) + (passwd-len cli-smallint)) + + +;;; CLI Functions needed +;;; SQLBindParameter +;;; SQLExecDirect +;;; SQLNumResultCols +;;; SQLDescribeCol +;;; SQLColAttribute +;;; SQLRowCount +;;; SQLBindCol +;;; SQLFetch +;;; SQLGetData +;;; SQLEndTran +;;; SQLFreeHandle +;;; SQLDisconnect +;;; SQLSetConnectAttr diff --git a/db-db2/db2-constants.lisp b/db-db2/db2-constants.lisp new file mode 100644 index 0000000..7429375 --- /dev/null +++ b/db-db2/db2-constants.lisp @@ -0,0 +1,26 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2-constants.lisp +;;;; Purpose: Constants for CLSQL Db2 interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + +(defconstant SQL_NULL_HANDLE nil) +(defconstant SQL_HANDLE_ENV 1) +(defconstant SQL_HANDLE_DBC 2) +(defconstant SQL_HANDLE_STMT 3) +(defconstant SQL_NTS -3) + +(defconstant SQL_ERROR -1) +(defconstant SQL_SUCCESS 0) +(defconstant SQL_SUCCESS_WITH_INFO 1) + diff --git a/db-db2/db2-loader.lisp b/db-db2/db2-loader.lisp new file mode 100644 index 0000000..5e5251b --- /dev/null +++ b/db-db2/db2-loader.lisp @@ -0,0 +1,52 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2-loader.lisp +;;;; Purpose: Foreign library loader for CLSQL Db2 interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + +(defparameter *db2-lib-path* + (let ((db2-home (getenv "DB2_HOME"))) + (when db2-home + (make-pathname :directory + (append + (pathname-directory + (parse-namestring (concatenate 'string db2-home "/"))) + (list "lib")))))) + +(defparameter *db2-library-filenames* + (if *db2-lib-path* + (list (merge-pathnames "libdb2" *db2-lib-path*) + "libdb2") + "libdb2")) + +(defvar *db2-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the Db2 client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *db2-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod clsql-sys:database-type-library-loaded ((database-type (eql :db2))) + *db2-library-loaded*) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :db2))) + (clsql-uffi:find-and-load-foreign-library *db2-library-filenames* + :module "clsql-db2" + :supporting-libraries + *db2-supporting-libraries*) + (setq *db2-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :db2) + + diff --git a/db-db2/db2-objects.lisp b/db-db2/db2-objects.lisp new file mode 100644 index 0000000..90e65b1 --- /dev/null +++ b/db-db2/db2-objects.lisp @@ -0,0 +1,15 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2-objects.lisp +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + diff --git a/db-db2/db2-package.lisp b/db-db2/db2-package.lisp new file mode 100644 index 0000000..c51dc6c --- /dev/null +++ b/db-db2/db2-package.lisp @@ -0,0 +1,23 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2-package.cl +;;;; Purpose: Package definition for CLSQL Db2 interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-db2 + (:use #:common-lisp #:clsql-sys #:clsql-uffi) + (:export #:db2-database + #:*db2-server-version* + #:*db2-so-load-path* + #:*db2-so-libraries*) + (:documentation "This is the CLSQL interface to Db2.")) diff --git a/db-db2/db2-sql.lisp b/db-db2/db2-sql.lisp new file mode 100644 index 0000000..aa7901e --- /dev/null +++ b/db-db2/db2-sql.lisp @@ -0,0 +1,70 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db2-sql.lisp +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + +(defmethod database-initialize-database-type ((database-type (eql :db2))) + t) + +(defclass db2-database (database) + ((henv :initform nil :allocation :class :initarg :henv :accessor henv) + (hdbc :initform nil :initarg :hdbc :accessor hdbc))) + + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :db2))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (dsn user password) connection-spec + (declare (ignore password)) + (concatenate 'string dsn "/" user))) + +(defmethod database-connect (connection-spec (database-type (eql :db2))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (server user password) connection-spec + (handler-case + (let ((db (make-instance 'db2-database + :name (database-name-from-spec connection-spec :db2) + :database-type :db2))) + (db2-connect db server user password) + db) + (error () ;; Init or Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :message "Connection failed"))))) + + +;; API Functions + +(uffi:def-type handle-type cli-handle) +(uffi:def-type handle-ptr-type (* cli-handle)) + +(defmacro deref-vp (foreign-object) + `(the handle-type (uffi:deref-pointer (the handle-ptr-type ,foreign-object) cli-handle))) + +(defun db2-connect (db server user password) + (let ((henv (uffi:allocate-foreign-object 'cli-handle)) + (hdbc (uffi:allocate-foreign-object 'cli-handle))) + (sql-alloc-handle SQL_HANDLE_ENV SQL_NULL_HANDLE henv) + (setf (slot-value db 'henv) henv) + (setf (slot-value db 'hdbc) hdbc) + + (sql-alloc-handle SQL_HANDLE_DBC (deref-vp henv) hdbc) + (uffi:with-cstrings ((native-server server) + (native-user user) + (native-password password)) + (sql-connect (deref-vp hdbc) + native-server SQL_NTS + native-user SQL_NTS + native-password SQL_NTS))) + db) diff --git a/db-db2/foreign-resources.lisp b/db-db2/foreign-resources.lisp new file mode 100644 index 0000000..c141bcd --- /dev/null +++ b/db-db2/foreign-resources.lisp @@ -0,0 +1,57 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-db2) + +(defparameter *foreign-resource-hash* (make-hash-table :test #'equal)) + +(defstruct (foreign-resource) + (type (error "Missing TYPE.") + :read-only t) + (sizeof (error "Missing SIZEOF.") + :read-only t) + (buffer (error "Missing BUFFER.") + :read-only t) + (in-use nil :type boolean)) + + +(defun %get-resource (type sizeof) + (let ((resources (gethash type *foreign-resource-hash*))) + (car (member-if + #'(lambda (res) + (and (= (foreign-resource-sizeof res) sizeof) + (not (foreign-resource-in-use res)))) + resources)))) + +(defun %insert-foreign-resource (type res) + (let ((resource (gethash type *foreign-resource-hash*))) + (setf (gethash type *foreign-resource-hash*) + (cons res resource)))) + +(defmacro acquire-foreign-resource (type &optional size) + `(let ((res (%get-resource ,type ,size))) + (unless res + (setf res (make-foreign-resource + :type ,type :sizeof ,size + :buffer (uffi:allocate-foreign-object ,type ,size))) + (%insert-foreign-resource ',type res)) + (claim-foreign-resource res))) + +(defun free-foreign-resource (ares) + (setf (foreign-resource-in-use ares) nil) + ares) + +(defun claim-foreign-resource (ares) + (setf (foreign-resource-in-use ares) t) + ares) + + + diff --git a/db-mysql/Makefile b/db-mysql/Makefile new file mode 100644 index 0000000..fed4bcc --- /dev/null +++ b/db-mysql/Makefile @@ -0,0 +1,85 @@ +#!/usr/bin/make +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL MySQL interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. + +SUBDIRS= + +include ../Makefile.common + +base=clsql_mysql +source=$(base).c +object=$(base).o +shared_lib=$(base).so +shared64_lib=$(base)64.so +dylib=$(base).dylib + +.PHONY: all +all: $(shared_lib) + +CFLAGS:=-I /usr/local/include/mysql -I /usr/include/mysql -I /sw/include/mysql -I /opt/local/include/mysql -I /opt/local/include/mysql55/mysql -I /usr/local/mysql/include +LDFLAGS:=-L/usr/local/lib64/mysql -L/usr/local/lib/mysql -L/usr/lib64/mysql -L/usr/lib/mysql -L/sw/lib -L/opt/local/lib/mysql -L/opt/local/lib/mysql5/mysql -L/usr/local/mysql/lib -L/usr/lib/gcc/i686-pc-cygwin/3.4.4 -lmysqlclient -lz -lc + +CFLAGS32:=-I /usr/local/include/mysql -I /usr/include/mysql -I /sw/include/mysql -I /opt/local/include/mysql -I /opt/local/include/mysql5/mysql -I /usr/local/mysql/include -m32 +LDFLAGS32:=-L/lib32 -L/usr/lib32 -lmysqlclient -lz -lc + +ifneq ($(OS_CYGWIN),0) + CFLAGS:=-I /cygdrive/c/Program\ Files/MySQL/MySQL\ Server\ 5.0/include + LDFLAGS:=-L/usr/local/lib64/mysql -L/usr/local/lib/mysql -L/usr/lib/mysql -L/sw/lib -L/opt/local/lib/mysql -L/usr/lib/gcc/i686-pc-cygwin/3.4.4 -lmysqlclient -lpthread -lz -lm -lgcc -lc +endif +ifneq ($(OS_DPKG_BUILDFLAGS),0) + CFLAGS:=$(CFLAGS) $(shell dpkg-buildflags --get CFLAGS) + LDFLAGS:=$(LDFLAGS) $(shell env DEB_BUILD_MAINT_OPTIONS=hardening=+all,-pie dpkg-buildflags --get LDFLAGS | sed "s/-Wl\|,/ /ig") +endif + +$(shared_lib): $(source) Makefile +ifneq ($(OS_AIX),0) + gcc $(CFLAGS) -c -D_BSD -D_NO_PROTO -D_NONSTD_TYPES -D_MBI=void $(source) + make_shared $(LDFLAGS) -o $(shared_lib) $(object) +else + ifneq ($(OS_SUNOS),0) + cc $(CFLAGS) -KPIC -c $(source) -o $(object) + cc -G $(object) $(LDFLAGS) -o $(shared_lib) + else + ifneq ($(OS_DARWIN64),0) + cc $(CFLAGS) -arch x86_64 -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress $(source) -o $(dylib) + cc -arch x86_64 -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz.dylib -o z.dylib + else + ifneq ($(OS_DARWIN),0) + cc $(CFLAGS) -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress $(source) -o $(dylib) + cc -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz.dylib -o z.dylib + else + ifneq ($(OS_CYGWIN),0) + gcc $(CFLAGS) -DWIN32 -c $(source) -o $(object) + ld -shared -soname=$(base) $(object) $(LDFLAGS) -o $(shared_lib) + else + ifneq ($(OS_LINUX64),0) + gcc $(CFLAGS) -fPIC -c $(source) -o $(object) + gcc $(LDFLAGS) -fPIC -shared -Wl,-soname=$(base) -lc $(object) -o $(shared64_lib) + rm $(object) + #@echo "Ensure that you have multiarch i386 build tools and libraries if you want to build 32-bit library" + #-gcc $(CFLAGS32) -fPIC -c $(source) -o $(object) + #-gcc $(LDFLAGS) -fPIC -shared -Wl,-soname=$(base) -lc $(object) $(LDFLAGS32) -o $(shared_lib) + else + gcc $(CFLAGS) -fPIC -c $(source) -o $(object) + gcc -fPIC -shared -Wl,-soname=$(base) $(object) $(LDFLAGS) -o $(shared_lib) + endif + endif + endif + endif + endif +endif + rm -f $(object) + +.PHONY: distclean +distclean: clean + @rm -f $(dylib) $(shared_lib) $(shared64_lib) $(object) z.dylib diff --git a/db-mysql/Makefile.msvc b/db-mysql/Makefile.msvc new file mode 100755 index 0000000..176c559 --- /dev/null +++ b/db-mysql/Makefile.msvc @@ -0,0 +1,40 @@ +# -*- Mode: Makefile -*- +########################################################################### +# FILE IDENTIFICATION +# +# Name: Makefile.msvc +# Purpose: Makefile for the CLSQL UFFI helper package (MSVC) +# Author: Kevin M. Rosenberg +# Created: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################### + + +BASE=clsql_mysql + +# Set to the directory where you have installed mysql's library +MYSQL_DIR=c:/Program Files/MySQL/MySQL Server 5.1/ +MYSQL_LIB_FILE=$(MYSQL_DIR)lib\opt\mysqlclient.lib +MYSQL_INCLUDE=$(MYSQL_DIR)include + +# Nothing to configure beyond here + +SRC=$(BASE).c +OBJ=$(BASE).obj +DLL=$(BASE).dll +LIB=$(BASE).lib + + +$(DLL): $(SRC) $(MYSQL_LIB_FILE) + echo "LD" + echo $(MYSQL_LIB_FILE) + cl /MD /LD -D_MT /DWIN32=1 /D__LCC__=1 /I"$(MYSQL_INCLUDE)" $(SRC) $(MYSQL_LIB_FILE) + del $(OBJ) $(BASE).exp + +clean: + del /q $(DLL) diff --git a/db-mysql/clsql_mysql.c b/db-mysql/clsql_mysql.c new file mode 100644 index 0000000..ebbb8e1 --- /dev/null +++ b/db-mysql/clsql_mysql.c @@ -0,0 +1,157 @@ +/**************************************************************************** + * FILE IDENTIFICATION + * + * Name: clsql-mysql.c + * Purpose: Helper functions for mysql.cl to handle 64-bit parts of API + * Programmer: Kevin M. Rosenberg + * Date Started: Mar 2002 + * + * This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg + * + * CLSQL users are granted the rights to distribute and use this software + * as governed by the terms of the Lisp Lesser GNU Public License + * (http://opensource.franz.com/preamble.html), also known as the LLGPL. + ***************************************************************************/ + +#if defined(WIN32)||defined(WIN64) +#include + +BOOL WINAPI DllEntryPoint(HINSTANCE hinstdll, DWORD fdwReason, + LPVOID lpvReserved) +{ + return 1; +} + +#define DLLEXPORT __declspec(dllexport) + +#else +#define DLLEXPORT +#endif + + +#include + +/* Need to assemble a 64-bit integer to send to MySQL */ +DLLEXPORT +void +clsql_mysql_data_seek (MYSQL_RES* res, unsigned int offset_high32, + unsigned int offset_low32) +{ + my_ulonglong offset; + + offset = offset_high32; + offset = offset << 32; + offset += offset_low32; + + mysql_data_seek (res, offset); +} + +/* The following functions are used to return 64-bit integers to Lisp. + They return the 32-bit low part and store in upper 32-bits in a + located sent via a pointer */ + +static const unsigned int bitmask_32bits = 0xFFFFFFFF; +#define lower_32bits(int64) ((unsigned int) int64 & bitmask_32bits) +#define upper_32bits(int64) ((unsigned int) (int64 >> 32)) + +DLLEXPORT +unsigned int +clsql_mysql_num_rows (MYSQL_RES* res, unsigned int* pHigh32) +{ + my_ulonglong nRows = mysql_num_rows (res); + *pHigh32 = upper_32bits(nRows); + return lower_32bits(nRows); +} + +DLLEXPORT +unsigned int +clsql_mysql_affected_rows (MYSQL* res, unsigned int* pHigh32) +{ + my_ulonglong nAffected = mysql_affected_rows (res); + *pHigh32 = upper_32bits(nAffected); + return lower_32bits(nAffected); +} + +DLLEXPORT +unsigned int +clsql_mysql_insert_id (MYSQL* mysql, unsigned int* pHigh32) +{ + my_ulonglong insert_id = mysql_insert_id (mysql); + *pHigh32 = upper_32bits(insert_id); + return lower_32bits(insert_id); +} + + +/* Accessor functions to hide the differences across MySQL versions */ + +DLLEXPORT +unsigned int +clsql_mysql_field_flags (MYSQL_FIELD* field) +{ + return field->flags; +} + + +DLLEXPORT +unsigned int +clsql_mysql_field_type (MYSQL_FIELD* field) +{ + return field->type; +} + +DLLEXPORT +char* +clsql_mysql_field_name (MYSQL_FIELD* field) +{ + return field->name; +} + +DLLEXPORT +unsigned long +clsql_mysql_field_length (MYSQL_FIELD* field) +{ + return field->length; +} + +DLLEXPORT +unsigned long +clsql_mysql_field_max_length (MYSQL_FIELD* field) +{ + return field->max_length; +} + + +#if MYSQL_VERSION_ID >= 40102 +#include + +DLLEXPORT +MYSQL_BIND* +allocate_bind (unsigned int n) +{ + return (MYSQL_BIND*) malloc (n * sizeof(MYSQL_BIND)); +} + +DLLEXPORT +void +bind_param (MYSQL_BIND bind[], unsigned int n, unsigned long length, unsigned short is_null, + void* buffer, unsigned short buffer_type, unsigned long buffer_length) +{ + *bind[n].length = length; + *bind[n].is_null = is_null; + bind[n].buffer = buffer; + bind[n].buffer_type = buffer_type; + bind[n].buffer_length = buffer_length; +} + + +DLLEXPORT +unsigned int +clsql_mysql_stmt_affected_rows (MYSQL_STMT* stmt, unsigned int* pHigh32) +{ + my_ulonglong nAffected = mysql_stmt_affected_rows (stmt); + *pHigh32 = upper_32bits(nAffected); + return lower_32bits(nAffected); +} + +#endif + diff --git a/db-mysql/mysql-api.lisp b/db-mysql/mysql-api.lisp new file mode 100644 index 0000000..19d1f91 --- /dev/null +++ b/db-mysql/mysql-api.lisp @@ -0,0 +1,559 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-api.lisp +;;;; Purpose: Low-level MySQL interface using UFFI +;;;; Programmers: Kevin M. Rosenberg based on +;;;; Original code by Pierre R. Mai +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2009 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:mysql) + +;;;; Modifications from original code +;;;; - Updated C-structures to conform to structures in MySQL 3.23.46 +;;;; - Changed from CMUCL interface to UFFI +;;;; - Added and call a C-helper file to support 64-bit integers +;;;; that are used in a few routines. +;;;; - Removed all references to interiors of C-structions, this will +;;;; increase robustness when MySQL's internal structures change. + +;;;; Type definitions + +;;; Basic Types + +(uffi:def-foreign-type mysql-socket :int) +(uffi:def-foreign-type mysql-bool :byte) +(uffi:def-foreign-type mysql-byte :unsigned-char) + +(uffi:def-enum mysql-net-type + (:tcp-ip + :socket + :named-pipe)) + +(uffi:def-array-pointer mysql-row (* :unsigned-char)) + +;;; MYSQL-FIELD +(uffi:def-enum mysql-field-types + (:decimal + :tiny + :short + :long + :float + :double + :null + :timestamp + :longlong + :int24 + :date + :time + :datetime + :year + :newdate + (:enum 247) + (:set 248) + (:tiny-blob 249) + (:medium-blob 250) + (:long-blob 251) + (:blob 252) + (:var-string 253) + (:string 254) + (:geometry 255))) + +(uffi:def-enum mysql-option + (:connect-timeout + :compress + :named-pipe + :init-command + :read-default-file + :read-default-group + :set-charset-dir + :set-charset-name + :local-infile + :protocol + :shared-memory-base-name + :read-timeout + :write-timeout + :use-result + :use-remote-connection + :use-embedded-connection + :guess-connection + :set-client-ip + :secure-auth + :report-data-truncation + :reconnect + :ssl-verify-server-cert)) + +(defvar +mysql-option-parameter-map+ + '((:connect-timeout . :uint-ptr) + (:compress . :none) + (:named-pipe . :none) + (:init-command . :char-ptr) + (:read-default-file . :char-ptr) + (:read-default-group . :char-ptr) + (:set-charset-dir . :char-ptr) + (:set-charset-name . :char-ptr) + (:local-infile . :uint-ptr) + (:protocol . :uint-ptr) + (:shared-memory-base-name . :char-ptr) + (:read-timeout . :uint-ptr) + (:write-timeout . :uint-ptr) + (:use-result . :none) + (:use-remote-connection . :none) + (:use-embedded-connection . :none) + (:guess-connection . :none) + (:set-client-ip . :char-ptr) + (:secure-auth . :boolean-ptr) + (:report-data-truncation . :boolean-ptr) + (:reconnect . :boolean-ptr) + (:ssl-verify-server-cert . :boolean-ptr))) + +(uffi:def-enum mysql-status + (:ready + :get-result + :use-result)) + +;;; Opaque pointers to mysql C-defined structures +(uffi:def-foreign-type mysql-mysql (* :void)) +(uffi:def-foreign-type mysql-mysql-res (* :void)) +(uffi:def-foreign-type mysql-field (* :void)) +(uffi:def-foreign-type mysql-bind (* :void)) + +;;;; The Foreign C routines +(declaim (inline mysql-init)) +(uffi:def-function "mysql_init" + ((mysql mysql-mysql)) + :module "mysql" + :returning mysql-mysql) + +;; Need to comment this out for LW 4.2.6 +;; ? bug in LW version +#-lispworks (declaim (inline mysql-real-connect)) +(uffi:def-function "mysql_real_connect" + ((mysql mysql-mysql) + (host :cstring) + (user :cstring) + (passwd :cstring) + (db :cstring) + (port :unsigned-int) + (unix-socket :cstring) + (clientflag :unsigned-long)) + :module "mysql" + :returning mysql-mysql) + +(declaim (inline mysql-close)) +(uffi:def-function "mysql_close" + ((sock mysql-mysql)) + :module "mysql" + :returning :void) + +(declaim (inline mysql-select-db)) +(uffi:def-function "mysql_select_db" + ((mysql mysql-mysql) + (db :cstring)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-query)) +(uffi:def-function "mysql_query" + ((mysql mysql-mysql) + (query :cstring)) + :module "mysql" + :returning :int) + + ;;; I doubt that this function is really useful for direct Lisp usage, +;;; but it is here for completeness... + +(declaim (inline mysql-real-query)) +(uffi:def-function "mysql_real_query" + ((mysql mysql-mysql) + (query :cstring) + (length :unsigned-int)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-shutdown)) +(uffi:def-function "mysql_shutdown" + ((mysql mysql-mysql)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-dump-debug-info)) +(uffi:def-function "mysql_dump_debug_info" + ((mysql mysql-mysql)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-refresh)) +(uffi:def-function "mysql_refresh" + ((mysql mysql-mysql) + (refresh-options :unsigned-int)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-kill)) +(uffi:def-function "mysql_kill" + ((mysql mysql-mysql) + (pid :unsigned-long)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-ping)) +(uffi:def-function "mysql_ping" + ((mysql mysql-mysql)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-stat)) +(uffi:def-function "mysql_stat" + ((mysql mysql-mysql)) + :module "mysql" + :returning :cstring) + +(declaim (inline mysql-get-server-info)) +(uffi:def-function "mysql_get_server_info" + ((mysql mysql-mysql)) + :module "mysql" + :returning :cstring) + +(declaim (inline mysql-get-host-info)) +(uffi:def-function "mysql_get_host_info" + ((mysql mysql-mysql)) + :module "mysql" + :returning :cstring) + +(declaim (inline mysql-get-proto-info)) +(uffi:def-function "mysql_get_proto_info" + ((mysql mysql-mysql)) + :module "mysql" + :returning :unsigned-int) + +(declaim (inline mysql-list-dbs)) +(uffi:def-function "mysql_list_dbs" + ((mysql mysql-mysql) + (wild :cstring)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-list-tables)) +(uffi:def-function "mysql_list_tables" + ((mysql mysql-mysql) + (wild :cstring)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-list-fields)) +(uffi:def-function "mysql_list_fields" + ((mysql mysql-mysql) + (table :cstring) + (wild :cstring)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-list-processes)) +(uffi:def-function "mysql_list_processes" + ((mysql mysql-mysql)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-store-result)) +(uffi:def-function "mysql_store_result" + ((mysql mysql-mysql)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-use-result)) +(uffi:def-function "mysql_use_result" + ((mysql mysql-mysql)) + :module "mysql" + :returning mysql-mysql-res) + +(declaim (inline mysql-options)) +(uffi:def-function "mysql_options" + ((mysql mysql-mysql) + (option mysql-option) + (arg :pointer-void)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-free-result)) +(uffi:def-function "mysql_free_result" + ((res mysql-mysql-res)) + :module "mysql" + :returning :void) + +(declaim (inline mysql-next-result)) +(uffi:def-function "mysql_next_result" + ((mysql mysql-mysql)) + :module "mysql" + :returning :int) + +(declaim (inline mysql-fetch-row)) +(uffi:def-function "mysql_fetch_row" + ((res mysql-mysql-res)) + :module "mysql" + :returning (* (* :unsigned-char))) + +(declaim (inline mysql-fetch-lengths)) +(uffi:def-function "mysql_fetch_lengths" + ((res mysql-mysql-res)) + :module "mysql" + :returning (* :unsigned-long)) + +(declaim (inline mysql-fetch-field)) +(uffi:def-function "mysql_fetch_field" + ((res mysql-mysql-res)) + :module "mysql" + :returning mysql-field) + +(declaim (inline mysql-field-seek)) +(uffi:def-function "mysql_field_seek" + ((res mysql-mysql-res) + (offset :unsigned-int)) + :module "mysql" + :returning :unsigned-int) + +(declaim (inline mysql-fetch-fields)) +(uffi:def-function "mysql_fetch_fields" + ((res mysql-mysql-res)) + :module "mysql" + :returning mysql-field) + +(declaim (inline mysql-fetch-field-direct)) +(uffi:def-function "mysql_fetch_field_direct" + ((res mysql-mysql-res) + (field-num :unsigned-int)) + :module "mysql" + :returning mysql-field) + +(declaim (inline mysql-escape-string)) +(uffi:def-function "mysql_escape_string" + ((to (* :unsigned-char)) + (from (* :unsigned-char)) + (length :unsigned-int)) + :module "mysql" + :returning :unsigned-int) + +(declaim (inline mysql-debug)) +(uffi:def-function "mysql_debug" + ((debug :cstring)) + :module "mysql" + :returning :void) + +(declaim (inline clsql-mysql-num-rows)) +(uffi:def-function "clsql_mysql_num_rows" + ((res mysql-mysql-res) + (p-high32 (* :unsigned-int))) + :module "clsql-mysql" + :returning :unsigned-int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-foreign-type mysql-stmt-ptr :pointer-void) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_init" + ((res mysql-mysql-res)) + :module "clsql-mysql" + :returning mysql-stmt-ptr) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_prepare" + ((stmt mysql-stmt-ptr) + (query :cstring) + (length :unsigned-long)) + :module "clsql-mysql" + :returning :int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_param_count" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :unsigned-int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_bind_param" + ((stmt mysql-stmt-ptr) + (bind mysql-bind)) + :module "clsql-mysql" + :returning :short) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_bind_result" + ((stmt mysql-stmt-ptr) + (bind mysql-bind)) + :module "clsql-mysql" + :returning :short) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_result_metadata" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning mysql-mysql-res) + + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_execute" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_store_result" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_fetch" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_free_result" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :short) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_close" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :short) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_errno" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :unsigned-int) + +#+(or mysql-client-v4.1 mysql-client-v5) +(uffi:def-function "mysql_stmt_error" + ((stmt mysql-stmt-ptr)) + :module "clsql-mysql" + :returning :cstring) + + +;;;; Equivalents of C Macro definitions for accessing various fields +;;;; in the internal MySQL Datastructures + + +(declaim (inline mysql-num-rows)) +(defun mysql-num-rows (res) + (uffi:with-foreign-object (p-high32 :unsigned-int) + (let ((low32 (clsql-mysql-num-rows res p-high32)) + (high32 (uffi:deref-pointer p-high32 :unsigned-int))) + (if (zerop high32) + low32 + (make-64-bit-integer high32 low32))))) + +(uffi:def-function "clsql_mysql_affected_rows" + ((mysql mysql-mysql) + (p-high32 (* :unsigned-int))) + :returning :unsigned-int + :module "clsql-mysql") + +(defun mysql-affected-rows (mysql) + (uffi:with-foreign-object (p-high32 :unsigned-int) + (let ((low32 (clsql-mysql-affected-rows mysql p-high32)) + (high32 (uffi:deref-pointer p-high32 :unsigned-int))) + (if (zerop high32) + low32 + (make-64-bit-integer high32 low32))))) + +(uffi:def-function "clsql_mysql_insert_id" + ((res mysql-mysql) + (p-high32 (* :unsigned-int))) + :returning :unsigned-int + :module "clsql-mysql") + +(defun mysql-insert-id (mysql) + (uffi:with-foreign-object (p-high32 :unsigned-int) + (let ((low32 (clsql-mysql-insert-id mysql p-high32)) + (high32 (uffi:deref-pointer p-high32 :unsigned-int))) + (if (zerop high32) + low32 + (make-64-bit-integer high32 low32))))) + + +(declaim (inline mysql-num-fields)) +(uffi:def-function "mysql_num_fields" + ((res mysql-mysql-res)) + :returning :unsigned-int + :module "mysql") + +(declaim (inline clsql-mysql-eof)) +(uffi:def-function ("mysql_eof" clsql-mysql-eof) + ((res mysql-mysql-res)) + :returning :char + :module "mysql") + +(declaim (inline mysql-eof)) +(defun mysql-eof (res) + (if (zerop (clsql-mysql-eof res)) + nil + t)) + +(declaim (inline mysql-error)) +(uffi:def-function ("mysql_error" mysql-error) + ((mysql mysql-mysql)) + :returning :cstring + :module "mysql") + +(declaim (inline mysql-error-string)) +(defun mysql-error-string (mysql) + (uffi:convert-from-cstring (mysql-error mysql))) + +(declaim (inline mysql-errno)) +(uffi:def-function "mysql_errno" + ((mysql mysql-mysql)) + :returning :unsigned-int + :module "mysql") + +(declaim (inline mysql-info)) +(uffi:def-function ("mysql_info" mysql-info) + ((mysql mysql-mysql)) + :returning :cstring + :module "mysql") + +(declaim (inline mysql-info-string)) +(defun mysql-info-string (mysql) + (uffi:convert-from-cstring (mysql-info mysql))) + +(declaim (inline clsql-mysql-data-seek)) +(uffi:def-function "clsql_mysql_data_seek" + ((res mysql-mysql-res) + (offset-high32 :unsigned-int) + (offset-low32 :unsigned-int)) + :module "clsql-mysql" + :returning :void) + +(declaim (inline clsql-mysql-field-name)) +(uffi:def-function "clsql_mysql_field_name" + ((res mysql-field)) + :module "clsql-mysql" + :returning :cstring) + +(declaim (inline clsql-mysql-field-flags)) +(uffi:def-function "clsql_mysql_field_flags" + ((res mysql-field)) + :module "clsql-mysql" + :returning :unsigned-int) + +(declaim (inline clsql-mysql-field-type)) +(uffi:def-function "clsql_mysql_field_type" + ((res mysql-field)) + :module "clsql-mysql" + :returning :unsigned-int) + +(defun mysql-data-seek (res offset) + (multiple-value-bind (high32 low32) (split-64-bit-integer offset) + (clsql-mysql-data-seek res high32 low32))) diff --git a/db-mysql/mysql-client-info.lisp b/db-mysql/mysql-client-info.lisp new file mode 100644 index 0000000..a944d45 --- /dev/null +++ b/db-mysql/mysql-client-info.lisp @@ -0,0 +1,50 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-client-info.lisp +;;;; Purpose: Check mysql client version +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: April 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:mysql) + +(declaim (inline mysql-get-client-info)) + +(defvar *mysql-client-info* nil) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (uffi:def-function ("mysql_get_client_info" mysql-get-client-info) + () + :module "mysql" + :returning :cstring) + + (setf *mysql-client-info* (uffi:convert-from-cstring (mysql-get-client-info))) + + + (when (and (stringp *mysql-client-info*) + (plusp (length *mysql-client-info*))) + (cond + ((eql (schar *mysql-client-info* 0) #\3) + (pushnew :mysql-client-v3 cl:*features*)) + ((eql (schar *mysql-client-info* 0) #\4) + (pushnew :mysql-client-v4 cl:*features*) + (when (and (>= (length *mysql-client-info*) 3) + (string-equal "4.1" *mysql-client-info* :end2 3)) + (pushnew :mysql-client-v4.1 cl:*features*))) + ((eql (schar *mysql-client-info* 0) #\5) + (pushnew :mysql-client-v5 cl:*features*) + (when (and (>= (length *mysql-client-info*) 3) + (string-equal "5.1" *mysql-client-info* :end2 3)) + (pushnew :mysql-client-v5.1 cl:*features*))) + ((eql (schar *mysql-client-info* 0) #\6) + (pushnew :mysql-client-v6 cl:*features*)) + (t + (format t "Warning: Unknown mysql client version '~A', verify proper operation." *mysql-client-info*))))) diff --git a/db-mysql/mysql-loader.lisp b/db-mysql/mysql-loader.lisp new file mode 100644 index 0000000..afea3e5 --- /dev/null +++ b/db-mysql/mysql-loader.lisp @@ -0,0 +1,51 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-loader.sql +;;;; Purpose: MySQL library loader using UFFI +;;;; Author: Kevin M. Rosenberg +;;;; Created: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:mysql) + +;; searches clsql_mysql64 to accomodate both 32-bit and 64-bit libraries on same system +(defparameter *clsql-mysql-library-candidate-names* + `(,@(when (> most-positive-fixnum (expt 2 32)) (list "clsql_mysql64")) + "clsql_mysql")) + +(defvar *mysql-library-candidate-names* + '("libmysqlclient" "libmysql")) + +(defvar *mysql-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the MySQL client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *mysql-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod clsql-sys:database-type-library-loaded ((database-type (eql :mysql))) + *mysql-library-loaded*) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :mysql))) + (unless *mysql-library-loaded* + (clsql:push-library-path clsql-mysql-system::*library-file-dir*) + + (clsql-uffi:find-and-load-foreign-library *mysql-library-candidate-names* + :module "mysql" + :supporting-libraries *mysql-supporting-libraries*) + + (clsql-uffi:find-and-load-foreign-library *clsql-mysql-library-candidate-names* + :module "clsql-mysql" + :supporting-libraries *mysql-supporting-libraries*) + (setq *mysql-library-loaded* t))) + +(clsql-sys:database-type-load-foreign :mysql) diff --git a/db-mysql/mysql-objects.lisp b/db-mysql/mysql-objects.lisp new file mode 100644 index 0000000..0a9e7b3 --- /dev/null +++ b/db-mysql/mysql-objects.lisp @@ -0,0 +1,25 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-objects.lisp +;;;; Purpose: CLSQL Object layer for MySQL +;;;; Created: May 2004 +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-mysql) + +(defmethod database-get-type-specifier ((type symbol) args database + (db-type (eql :mysql))) + (declare (ignore args database db-type)) + (case type + (wall-time "DATETIME") + (tinyint "TINYINT") + (smallint "SMALLINT") + (mediumint "MEDIUMINT") + (t (call-next-method)))) + diff --git a/db-mysql/mysql-package.lisp b/db-mysql/mysql-package.lisp new file mode 100644 index 0000000..74a1955 --- /dev/null +++ b/db-mysql/mysql-package.lisp @@ -0,0 +1,153 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-package.cl +;;;; Purpose: Package definition for low-level MySQL interface +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:mysql + (:use #:common-lisp #:clsql-uffi) + (:export + #:database-library-loaded + + #:mysql-socket + #:mysql-book + #:mysql-byte + #:mysql-net-type + #:mysql-net-type#tcp-ip + #:mysql-net-type#socket + #:mysql-net-type#named-pipe + #:mysql-net + #:mysql-used-mem + #:mysql-mem-root + #:mysql-field-types + #:mysql-field-types#decimal + #:mysql-field-types#tiny + #:mysql-field-types#short + #:mysql-field-types#long + #:mysql-field-types#float + #:mysql-field-types#double + #:mysql-field-types#null + #:mysql-field-types#timestamp + #:mysql-field-types#longlong + #:mysql-field-types#int24 + #:mysql-field-types#date + #:mysql-field-types#time + #:mysql-field-types#datetime + #:mysql-field-types#year + #:mysql-field-types#newdate + #:mysql-field-types#enum + #:mysql-field-types#tiny-blob + #:mysql-field-types#medium-blob + #:mysql-field-types#long-blob + #:mysql-field-types#blob + #:mysql-field-types#var-string + #:mysql-field-types#string + #:mysql-field + #:mysql-row + #:mysql-field-offset + #:mysql-row-offset + #:mysql-field-vector + #:mysql-data + #:mysql-options + #:mysql-mysql-option + #:mysql-mysql-option#connect-timeout + #:mysql-mysql-option#compress + #:mysql-mysql-option#named-pipe + #:mysql-mysql-option#init-command + #:mysql-mysql-option#read-default-file + #:mysql-mysql-option#read-default-group + #:mysql-status + #:mysql-status#ready + #:mysql-status#get-ready + #:mysql-status#use-result + #:mysql-mysql + #:mysql-mysql-res + + ;; functions + #:mysql-init + #:mysql-connect + #:mysql-real-connect + #:mysql-close + #:mysql-select-db + #:mysql-query + #:mysql-real-query + #:mysql-create-db + #:mysql-drop-db + #:mysql-shutdown + #:mysql-dump-debug-info + #:mysql-refresh + #:mysql-kill + #:mysql-ping + #:mysql-stat + #:mysql-get-server-info + #:mysql-get-client-info + #:mysql-get-host-info + #:mysql-get-proto-info + #:mysql-list-dbs + #:mysql-list-tables + #:mysql-list-fields + #:mysql-list-processes + #:mysql-store-result + #:mysql-use-result + #:mysql-options + #:mysql-free-result + #:mysql-next-result + #:mysql-row-seek + #:mysql-field-seek + #:mysql-fetch-row + #:mysql-fetch-lengths + #:mysql-fetch-field + #:mysql-fetch-fields + #:mysql-fetch-field-direct + #:mysql-escape-string + #:mysql-debug + #:mysql-num-rows + #:mysql-num-fields + #:mysql-affected-rows + #:mysql-insert-id + #:mysql-eof + #:mysql-error + #:mysql-error-string + #:mysql-errno + #:mysql-info + #:mysql-info-string + #:mysql-data-seek + + #:mysql-time + #:mysql-bind + #:mysql-stmt-param-count + #:mysql-stmt-prepare + #:mysql-stmt-execute + #:mysql-stmt-store-result + #:mysql-stmt-init + #:mysql-stmt-close-result + #:mysql-stmt-free-result + #:mysql-stmt + #:mysql-stmt-result-metadata + #:mysql-stmt-fetch + #:mysql-stmt-bind-param + #:mysql-stmt-bind-result + #:mysql-stmt-close + #:mysql-stmt-errno + #:mysql-stmt-error + + #:make-64-bit-integer + #:clsql-mysql-field-name + #:clsql-mysql-field-type + #:clsql-mysql-field-flags + + #:+mysql-option-parameter-map+ + ) + (:documentation "This is the low-level interface MySQL.")) diff --git a/db-mysql/mysql-sql.lisp b/db-mysql/mysql-sql.lisp new file mode 100644 index 0000000..1f54d44 --- /dev/null +++ b/db-mysql/mysql-sql.lisp @@ -0,0 +1,848 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: mysql-sql.lisp +;;;; Purpose: High-level MySQL interface using UFFI +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-mysql + (:use #:common-lisp #:clsql-sys #:mysql #:clsql-uffi) + (:export #:mysql-database) + (:import-from :clsql-sys + :escaped :unescaped :combine-database-identifiers + :escaped-database-identifier :unescaped-database-identifier :database-identifier + :%sequence-name-to-table :%table-name-to-sequence-name) + (:documentation "This is the CLSQL interface to MySQL.")) + +(in-package #:clsql-mysql) + +;;; Field conversion functions + +(defun result-field-names (res-ptr) + (let ((names '())) + (mysql-field-seek res-ptr 0) + (loop + (let ((field (mysql-fetch-field res-ptr))) + (when (uffi:null-pointer-p field) (return)) + (push (uffi:convert-from-cstring (clsql-mysql-field-name field)) names))) + (nreverse names))) + +(defun make-type-list-for-auto (res-ptr) + (let ((new-types '())) + (mysql-field-seek res-ptr 0) + (loop + (let ((field (mysql-fetch-field res-ptr))) + (when (uffi:null-pointer-p field) (return)) + (let* ((flags (clsql-mysql-field-flags field)) + (unsigned (plusp (logand flags 32))) + (type (clsql-mysql-field-type field))) + (push + (case type + ((#.mysql-field-types#tiny + #.mysql-field-types#short + #.mysql-field-types#int24) + (if unsigned + :uint32 + :int32)) + (#.mysql-field-types#long + (if unsigned + :uint + :int)) + (#.mysql-field-types#longlong + (if unsigned + :uint64 + :int64)) + ((#.mysql-field-types#double + #.mysql-field-types#float + #.mysql-field-types#decimal) + :double) + (otherwise + t)) + new-types)))) + (nreverse new-types))) + +(defun canonicalize-types (types res-ptr) + (when types + (let ((auto-list (make-type-list-for-auto res-ptr))) + (cond + ((listp types) + (canonicalize-type-list types auto-list)) + ((eq types :auto) + auto-list) + (t + nil))))) + +(defmethod database-initialize-database-type ((database-type (eql :mysql))) + t) + +;;(uffi:def-type mysql-mysql-ptr-def (* mysql-mysql)) +;;(uffi:def-type mysql-mysql-res-ptr-def (* mysql-mysql-res)) +(uffi:def-type mysql-mysql-ptr-def mysql-mysql) +(uffi:def-type mysql-mysql-res-ptr-def mysql-mysql-res) +(uffi:def-type mysql-row-def mysql-row) + +(defclass mysql-database (database) + ((mysql-ptr :accessor database-mysql-ptr :initarg :mysql-ptr + :type mysql-mysql-ptr-def) + (server-info :accessor database-server-info :initarg :server-info + :type string))) + +(defmethod database-type ((database mysql-database)) + :mysql) + +(defmethod database-name-from-spec (connection-spec (database-type (eql :mysql))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options)) + (destructuring-bind (host db user password &optional port options) connection-spec + (declare (ignore password options)) + (concatenate 'string + (etypecase host + (null "localhost") + (pathname (namestring host)) + (string host)) + (if port + (concatenate 'string + ":" + (etypecase port + (integer (write-to-string port)) + (string port))) + "") + "/" db "/" user))) + +(defun lookup-option-code (option) + (if (assoc option +mysql-option-parameter-map+) + (symbol-value (intern + (concatenate 'string (symbol-name-default-case "mysql-option#") + (symbol-name option)) + (symbol-name '#:mysql))) + (progn + (warn "Unknown mysql option name ~A - ignoring.~%" option) + nil))) + +(defun set-mysql-options (mysql-ptr options) + (flet ((lookup-option-type (option) + (cdr (assoc option +mysql-option-parameter-map+)))) + (dolist (option options) + (if (atom option) + (let ((option-code (lookup-option-code option))) + (when option-code + (mysql-options mysql-ptr option-code uffi:+null-cstring-pointer+))) + (destructuring-bind (name . value) option + (let ((option-code (lookup-option-code name))) + (when option-code + (case (lookup-option-type name) + (:none + (mysql-options mysql-ptr option-code uffi:+null-cstring-pointer+)) + (:char-ptr + (if (stringp value) + (uffi:with-foreign-string (fs value) + (mysql-options mysql-ptr option-code fs)) + (warn "Expecting string argument for mysql option ~A, got ~A ~ +- ignoring.~%" + name value))) + (:uint-ptr + (if (integerp value) + (uffi:with-foreign-object (fo :unsigned-int) + (setf (uffi:deref-pointer fo :unsigned-int) value) + (mysql-options mysql-ptr option-code fo)) + (warn "Expecting integer argument for mysql option ~A, got ~A ~ +- ignoring.~%" + name value))) + (:boolean-ptr + (uffi:with-foreign-object (fo :byte) + (setf (uffi:deref-pointer fo :byte) + (case value + ((nil 0) 0) + (t 1))) + (mysql-options mysql-ptr option-code fo))))))))))) + +(defmethod database-connect (connection-spec (database-type (eql :mysql))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options)) + (destructuring-bind (host db user password &optional port options) connection-spec + (let ((mysql-ptr (mysql-init (uffi:make-null-pointer 'mysql-mysql))) + (socket nil)) + (if (uffi:null-pointer-p mysql-ptr) + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr)) + (uffi:with-cstrings ((host-native host) + (user-native user) + (password-native password) + (db-native db) + (socket-native socket)) + (when options + (set-mysql-options mysql-ptr options)) + (let ((error-occurred nil)) + (unwind-protect + (if (uffi:null-pointer-p + (mysql-real-connect + mysql-ptr host-native user-native password-native + db-native + (etypecase port + (null 0) + (integer port) + (string (parse-integer port))) + socket-native 0)) + (progn + (setq error-occurred t) + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr))) + (let* ((db + (make-instance 'mysql-database + :name (database-name-from-spec connection-spec + database-type) + :database-type :mysql + :connection-spec connection-spec + :server-info (uffi:convert-from-cstring + (mysql:mysql-get-server-info mysql-ptr)) + :mysql-ptr mysql-ptr)) + (cmd "SET SESSION sql_mode='ANSI'")) + (uffi:with-cstring (cmd-cs cmd) + (if (zerop (mysql-real-query mysql-ptr cmd-cs (uffi:foreign-encoded-octet-count + cmd :encoding (encoding db)))) + db + (progn + (warn "Error setting ANSI mode for MySQL.") + db))))) + (when error-occurred (mysql-close mysql-ptr))))))))) + + +(defmethod database-disconnect ((database mysql-database)) + (mysql-close (database-mysql-ptr database)) + (setf (database-mysql-ptr database) nil) + t) + +(defmethod database-execute-command (sql-expression (database mysql-database)) + (uffi:with-cstring (sql-native sql-expression) + (let ((mysql-ptr (database-mysql-ptr database))) + (declare (type mysql-mysql-ptr-def mysql-ptr)) + (if (zerop (mysql-real-query mysql-ptr sql-native + (uffi:foreign-encoded-octet-count + sql-expression :encoding (encoding database)))) + t + (error 'sql-database-data-error + :database database + :expression sql-expression + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr)))))) + + +(defmethod database-query (query-expression (database mysql-database) + result-types field-names) + (declare (optimize (speed 3))) + (let ((mysql-ptr (database-mysql-ptr database)) + (results nil) ;; all the results and column-names in reverse-order + (res-ptr nil) + (num-fields 0)) + (declare (type mysql-mysql-ptr-def mysql-ptr) + (type (or null mysql-mysql-res-ptr-def) res-ptr) + (fixnum num-fields)) + (when (database-execute-command query-expression database) + (labels + ((get-row (row lengths) + "Pull a single row value from the results" + (loop for i from 0 below num-fields + collect + (convert-raw-field + (uffi:deref-array row '(:array (* :unsigned-char)) + (the fixnum i)) + (nth i result-types) + :length + (uffi:deref-array lengths '(:array :unsigned-long) + (the fixnum i)) + :encoding (encoding database)))) + (get-result-rows () + "get all the rows out of the now valid results set" + (loop for row = (mysql-fetch-row res-ptr) + for lengths = (mysql-fetch-lengths res-ptr) + until (uffi:null-pointer-p row) + collect (get-row row lengths))) + (do-result-set () + "for a mysql-ptr, grab and return a results set" + (setf res-ptr (mysql-use-result mysql-ptr)) + (cond + ((or (null res-ptr) (uffi:null-pointer-p res-ptr)) + (unless (zerop (mysql-errno mysql-ptr)) + ;;from http://dev.mysql.com/doc/refman/5.0/en/mysql-field-count.html + ;; if mysql_use_result or mysql_store_result return a null ptr, + ;; we use a mysql_errno check to see if it had a problem or just + ;; was a query without a result. If no error, just return nil. + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr)))) + (t + (unwind-protect + (progn (setf num-fields (mysql-num-fields res-ptr) + result-types (canonicalize-types + result-types res-ptr)) + (push (get-result-rows) results) + (push (when field-names + (result-field-names res-ptr)) + results)) + (mysql-free-result res-ptr)))))) + + (loop + do (do-result-set) + while (let ((next (mysql-next-result mysql-ptr))) + (case next + (0 t) ;Successful and there are more results + (-1 nil) ;Successful and there are no more results + (t nil) ;errors + ))) + (values-list (nreverse results)))))) + + +(defstruct mysql-result-set + (res-ptr (uffi:make-null-pointer 'mysql-mysql-res) :type mysql-mysql-res-ptr-def) + (types nil :type list) + (num-fields 0 :type fixnum) + (full-set nil :type boolean)) + + +(defmethod database-query-result-set ((query-expression string) + (database mysql-database) + &key full-set result-types) + ;; TODO: REFACTOR THIS IN TERMS OF database-query or vice-versa + ;; This doesnt seem to free database results reliably, dont think + ;; that we should allow that particularly, OTOH, dont know how + ;; we support cursoring without it + (let ((mysql-ptr (database-mysql-ptr database))) + (declare (type mysql-mysql-ptr-def mysql-ptr)) + (when (database-execute-command query-expression database) + (let ((res-ptr (if full-set + (mysql-store-result mysql-ptr) + (mysql-use-result mysql-ptr)))) + (declare (type mysql-mysql-res-ptr-def res-ptr)) + (if (not (uffi:null-pointer-p res-ptr)) + (let* ((num-fields (mysql-num-fields res-ptr)) + (result-set (make-mysql-result-set + :res-ptr res-ptr + :num-fields num-fields + :full-set full-set + :types + (canonicalize-types + result-types res-ptr)))) + (if full-set + (values result-set + num-fields + (mysql-num-rows res-ptr)) + (values result-set + num-fields))) + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr)))) + ))) + +(defmethod database-dump-result-set (result-set (database mysql-database)) + (mysql-free-result (mysql-result-set-res-ptr result-set)) + t) + + +(defmethod database-store-next-row (result-set (database mysql-database) list) + (let* ((res-ptr (mysql-result-set-res-ptr result-set)) + (row (mysql-fetch-row res-ptr)) + (lengths (mysql-fetch-lengths res-ptr)) + (types (mysql-result-set-types result-set))) + (declare (type mysql-mysql-res-ptr-def res-ptr) + (type mysql-row-def row)) + (unless (uffi:null-pointer-p row) + (loop for i from 0 below (mysql-result-set-num-fields result-set) + for rest on list + do + (setf (car rest) + (convert-raw-field + (uffi:deref-array row '(:array (* :unsigned-char)) i) + (nth i types) + :length + (uffi:deref-array lengths '(:array :unsigned-long) i) + :encoding (encoding database)))) + list))) + + +;; Table and attribute introspection + +(defmethod database-list-tables ((database mysql-database) &key (owner nil)) + (declare (ignore owner)) + (cond + ((eql #\5 (char (database-server-info database) 0)) + (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil) + when (and (string-equal type "base table") + (not (and (>= (length name) 11) + (string-equal (subseq name 0 11) "_CLSQL_SEQ_")))) + collect name)) + (t + (remove-if #'(lambda (s) + (and (>= (length s) 11) + (string-equal (subseq s 0 11) "_CLSQL_SEQ_"))) + (mapcar #'car (database-query "SHOW TABLES" database nil nil)))))) + +(defmethod database-list-views ((database mysql-database) + &key (owner nil)) + (declare (ignore owner)) + (cond + ((eql #\5 (char (database-server-info database) 0)) + (loop for (name type) in (database-query "SHOW FULL TABLES" database nil nil) + when (string-equal type "view") + collect name)) + (t + nil))) + +(defmethod database-list-indexes ((database mysql-database) + &key (owner nil)) + (let ((result '())) + (dolist (table (database-list-tables database :owner owner) result) + (setq result + (append (database-list-table-indexes table database :owner owner) + result))))) + +(defmethod database-list-table-indexes (table (database mysql-database) + &key (owner nil)) + (declare (ignore owner)) + (do ((results nil) + (rows (database-query + (format nil "SHOW INDEX FROM ~A" (escaped-database-identifier + table database)) + database nil nil) + (cdr rows))) + ((null rows) (nreverse results)) + (let ((col (nth 2 (car rows)))) + (unless (find col results :test #'string-equal) + (push col results))))) + +(defmethod database-list-attributes ((table clsql-sys::%database-identifier) + (database mysql-database) + &key (owner nil) + &aux (table (unescaped-database-identifier table))) + (declare (ignore owner)) + (mapcar #'car + (database-query + (format nil "SHOW COLUMNS FROM ~A" (escaped-database-identifier + table database)) + database nil nil))) + +(defmethod database-attribute-type ((attribute clsql-sys::%database-identifier) + (table clsql-sys::%database-identifier) + (database mysql-database) + &key (owner nil) + &aux (table (unescaped-database-identifier table)) + (attribute (unescaped-database-identifier attribute))) + (declare (ignore owner)) + (let ((row (car (database-query + (format nil + "SHOW COLUMNS FROM ~A LIKE '~A'" + (escaped-database-identifier + table database) + (unescaped-database-identifier + attribute database)) + database nil nil)))) + (let* ((raw-type (second row)) + (null (third row)) + (start-length (position #\( raw-type)) + (type (if start-length + (subseq raw-type 0 start-length) + raw-type)) + (length (when start-length + (parse-integer (subseq raw-type (1+ start-length)) + :junk-allowed t)))) + (when type + (values (ensure-keyword type) length nil (if (string-equal null "YES") 1 0)))))) + +;;; Sequence functions + +(defmethod database-create-sequence (sequence-name + (database mysql-database)) + (let ((table-name (%sequence-name-to-table sequence-name database))) + (database-execute-command + (concatenate 'string "CREATE TABLE " table-name + " (id int NOT NULL PRIMARY KEY AUTO_INCREMENT)") + database) + (database-execute-command + (concatenate 'string "INSERT INTO " table-name + " VALUES (-1)") + database))) + +(defmethod database-drop-sequence (sequence-name + (database mysql-database)) + (database-execute-command + (concatenate 'string "DROP TABLE " + (%sequence-name-to-table sequence-name database)) + database)) + +(defmethod database-list-sequences ((database mysql-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcan #'(lambda (s) + (let ((sn (%table-name-to-sequence-name (car s)))) + (and sn (list (car s) sn)))) + (database-query "SHOW TABLES" database nil nil))) + +(defmethod database-set-sequence-position (sequence-name + (position integer) + (database mysql-database)) + (database-execute-command + (format nil "UPDATE ~A SET id=~A" (%sequence-name-to-table sequence-name database) + position) + database) + (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database))) + +(defmethod database-sequence-next (sequence-name (database mysql-database)) + (without-interrupts + (database-execute-command + (concatenate 'string "UPDATE " (%sequence-name-to-table sequence-name database) + " SET id=LAST_INSERT_ID(id+1)") + database) + (mysql:mysql-insert-id (clsql-mysql::database-mysql-ptr database)))) + +(defmethod database-sequence-last (sequence-name (database mysql-database)) + (without-interrupts + (caar (database-query + (concatenate 'string "SELECT id from " + (%sequence-name-to-table sequence-name database)) + database :auto nil)))) + +(defmethod database-last-auto-increment-id ((database mysql-database) table column) + (declare (ignore table column)) + (car (query "SELECT LAST_INSERT_ID();" + :flatp t :field-names nil + :database database))) + +(defmethod database-create (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password) connection-spec + (let ((database (database-connect (list host "" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "create database ~A" name) database) + (database-disconnect database))))) + +(defmethod database-destroy (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password) connection-spec + (let ((database (database-connect (list host "" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "drop database ~A" name) database) + (database-disconnect database))))) + +(defmethod database-probe (connection-spec (type (eql :mysql))) + (when (find (second connection-spec) (database-list connection-spec type) + :test #'string-equal) + t)) + +(defmethod database-list (connection-spec (type (eql :mysql))) + (destructuring-bind (host name user password &optional port options) connection-spec + (declare (ignore options)) + (let ((database (database-connect (list host (or name "mysql") + user password port) type))) + (unwind-protect + (progn + (setf (slot-value database 'clsql-sys::state) :open) + (mapcar #'car (database-query "show databases" database :auto nil))) + (progn + (database-disconnect database) + (setf (slot-value database 'clsql-sys::state) :closed)))))) + + +;;; Prepared statements + +(defclass mysql-stmt () + ((database :initarg :database :reader database) + (stmt :initarg :stmt :accessor stmt) + (input-bind :initarg :input-bind :reader input-bind) + (output-bind :initarg :output-bind :reader output-bind) + (types :initarg :types :reader types) + (result-set :initarg :result-set :reader result-set) + (num-fields :initarg :num-fields :reader num-fields) + (field-names :initarg :field-names :accessor stmt-field-names) + (length-ptr :initarg :length-ptr :reader length-ptr) + (is-null-ptr :initarg :is-null-ptr :reader is-null-ptr) + (result-types :initarg :result-types :reader result-types))) + +(defun clsql-type->mysql-type (type) + (cond + ((in type :null) mysql-field-types#null) + ((in type :int :integer) mysql-field-types#long) + ((in type :short) mysql-field-types#short) + ((in type :bigint) mysql-field-types#longlong) + ((in type :float :double :number) mysql-field-types#double) + ((and (consp type) (in (car type) :char :string :varchar)) mysql-field-types#var-string) + ((or (eq type :blob) (and (consp type) (in (car type) :blob))) mysql-field-types#var-string) + (t + (error 'sql-user-error + :message + (format nil "Unknown clsql type ~A." type))))) + +#+mysql-client-v4.1 +(defmethod database-prepare (sql-stmt types (database mysql-database) result-types field-names) + (let* ((mysql-ptr (database-mysql-ptr database)) + (stmt (mysql-stmt-init mysql-ptr))) + (when (uffi:null-pointer-p stmt) + (error 'sql-database-error + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr))) + + (uffi:with-cstring (native-query sql-stmt) + (unless (zerop (mysql-stmt-prepare stmt native-query (uffi:foreign-encoded-octet-count + sql-stmt :encoding (encoding database)))) + (mysql-stmt-close stmt) + (error 'sql-database-error + :error-id (mysql-errno mysql-ptr) + :message (mysql-error-string mysql-ptr)))) + + (unless (= (mysql-stmt-param-count stmt) (length types)) + (mysql-stmt-close stmt) + (error 'sql-database-error + :message + (format nil "Mysql param count (~D) does not match number of types (~D)" + (mysql-stmt-param-count stmt) (length types)))) + + (let ((rs (mysql-stmt-result-metadata stmt))) + (when (uffi:null-pointer-p rs) + (warn "mysql_stmt_result_metadata returned NULL") + #+nil + (mysql-stmt-close stmt) + #+nil + (error 'sql-database-error + :message "mysql_stmt_result_metadata returned NULL")) + + (let ((input-bind (uffi:allocate-foreign-object 'mysql-bind (length types))) + (mysql-types (mapcar 'clsql-type->mysql-type types)) + field-vec num-fields is-null-ptr output-bind length-ptr) + + (print 'a) + (dotimes (i (length types)) + (let* ((binding (uffi:deref-array input-bind '(:array mysql-bind) i))) + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) + (nth i mysql-types)) + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0))) + + (print 'b) + (unless (uffi:null-pointer-p rs) + (setq field-vec (mysql-fetch-fields rs) + num-fields (mysql-num-fields rs) + is-null-ptr (uffi:allocate-foreign-object :byte num-fields) + output-bind (uffi:allocate-foreign-object 'mysql-bind num-fields) + length-ptr (uffi:allocate-foreign-object :unsigned-long num-fields)) + (dotimes (i num-fields) + (declare (fixnum i)) + (let* ((field (uffi:deref-array field-vec '(:array mysql-field) i)) + (type (uffi:get-slot-value field 'mysql-field 'type)) + (binding (uffi:deref-array output-bind '(:array mysql-bind) i))) + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-type) type) + + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 0) + #+need-to-allocate-foreign-object-for-this + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::is-null) + (+ i (uffi:pointer-address is-null-ptr))) + #+need-to-allocate-foreign-object-for-this + (setf (uffi:get-slot-value binding 'mysql-bind 'length) + (+ (* i 8) (uffi:pointer-address length-ptr))) + + (case type + ((#.mysql-field-types#var-string #.mysql-field-types#string + #.mysql-field-types#tiny-blob #.mysql-field-types#blob + #.mysql-field-types#medium-blob #.mysql-field-types#long-blob) + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer-length) 1024) + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :unsigned-char 1024))) + (#.mysql-field-types#tiny + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :byte))) + (#.mysql-field-types#short + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :short))) + (#.mysql-field-types#long + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + ;; segfaults if supply :int on amd64 + (uffi:allocate-foreign-object :long))) + #+64bit + (#.mysql-field-types#longlong + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :long))) + (#.mysql-field-types#float + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :float))) + (#.mysql-field-types#double + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) + (uffi:allocate-foreign-object :double))) + ((#.mysql-field-types#time #.mysql-field-types#date + #.mysql-field-types#datetime #.mysql-field-types#timestamp) + (uffi:allocate-foreign-object 'mysql-time)) + (t + (error "mysql type ~D not supported." type))))) + + (unless (zerop (mysql-stmt-bind-result stmt output-bind)) + (mysql-stmt-close stmt) + (error 'sql-database-error + :error-id (mysql-stmt-errno stmt) + :message (uffi:convert-from-cstring + (mysql-stmt-error stmt))))) + + (make-instance 'mysql-stmt + :database database + :stmt stmt + :num-fields num-fields + :input-bind input-bind + :output-bind output-bind + :result-set rs + :result-types result-types + :length-ptr length-ptr + :is-null-ptr is-null-ptr + :types mysql-types + :field-names field-names))))) + +#+mysql-client-v4.1 +(defmethod database-bind-parameter ((stmt mysql-stmt) position value) + ;; FIXME: will need to allocate bind structure. This should probably be + ;; done in C since the API is not mature and may change + (let ((binding (uffi:deref-array (input-bind stmt) '(:array mysql-bind) (1- position))) + (type (nth (1- position) (types stmt)))) + (setf (uffi:get-slot-value binding 'mysql-bind 'length) 0) + (cond + ((null value) + (when (is-null-ptr stmt) + (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 1))) + (t + (when (is-null-ptr stmt) + (setf (uffi:deref-array (is-null-ptr stmt) '(:array :byte) (1- position)) 0)) + (case type + (#.mysql-field-types#long + (setf (uffi:get-slot-value binding 'mysql-bind 'mysql::buffer) value)) + (t + (warn "Unknown input bind type ~D." type)) + ))))) + +#+mysql-client-v4.1 +(defmethod database-run-prepared ((stmt mysql-stmt)) + (print 'a1) + (when (input-bind stmt) + (unless (zerop (mysql-stmt-bind-param (stmt stmt) (input-bind stmt))) + (error 'sql-database-error + :error-id (mysql-stmt-errno (stmt stmt)) + :message (uffi:convert-from-cstring + (mysql-stmt-error (stmt stmt)))))) + (print 'a2) + (unless (zerop (mysql-stmt-execute (stmt stmt))) + (error 'sql-database-error + :error-id (mysql-stmt-errno (stmt stmt)) + :message (uffi:convert-from-cstring + (mysql-stmt-error (stmt stmt))))) + (print 'a3) + (unless (zerop (mysql-stmt-store-result (stmt stmt))) + (error 'sql-database-error + :error-id (mysql-stmt-errno (stmt stmt)) + :message (uffi:convert-from-cstring + (mysql-stmt-error (stmt stmt))))) + (database-fetch-prepared-rows stmt)) + +#+mysql-client-v4.1 +(defun database-fetch-prepared-rows (stmt) + (do ((rc (mysql-stmt-fetch (stmt stmt)) (mysql-stmt-fetch (stmt stmt))) + (num-fields (num-fields stmt)) + (rows '())) + ((not (zerop rc)) (nreverse rows)) + (push + (loop for i from 0 below num-fields + collect + (let ((is-null + (not (zerop (uffi:ensure-char-integer + (uffi:deref-array (is-null-ptr stmt) '(:array :byte) i)))))) + (unless is-null + (let* ((bind (uffi:deref-array (output-bind stmt) '(:array mysql-bind) i)) + (type (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer-type)) + (buffer (uffi:get-slot-value bind 'mysql-bind 'mysql::buffer))) + (case type + ((#.mysql-field-types#var-string #.mysql-field-types#string + #.mysql-field-types#tiny-blob #.mysql-field-types#blob + #.mysql-field-types#medium-blob #.mysql-field-types#long-blob) + (uffi:convert-from-foreign-string buffer :encoding (encoding (database stmt)))) + (#.mysql-field-types#tiny + (uffi:ensure-char-integer + (uffi:deref-pointer buffer :byte))) + (#.mysql-field-types#short + (uffi:deref-pointer buffer :short)) + (#.mysql-field-types#long + (uffi:deref-pointer buffer :int)) + #+64bit + (#.mysql-field-types#longlong + (uffi:deref-pointer buffer :long)) + (#.mysql-field-types#float + (uffi:deref-pointer buffer :float)) + (#.mysql-field-types#double + (uffi:deref-pointer buffer :double)) + ((#.mysql-field-types#time #.mysql-field-types#date + #.mysql-field-types#datetime #.mysql-field-types#timestamp) + (let ((year (uffi:get-slot-value buffer 'mysql-time 'mysql::year)) + (month (uffi:get-slot-value buffer 'mysql-time 'mysql::month)) + (day (uffi:get-slot-value buffer 'mysql-time 'mysql::day)) + (hour (uffi:get-slot-value buffer 'mysql-time 'mysql::hour)) + (minute (uffi:get-slot-value buffer 'mysql-time 'mysql::minute)) + (second (uffi:get-slot-value buffer 'mysql-time 'mysql::second))) + (db-timestring + (make-time :year year :month month :day day :hour hour + :minute minute :second second)))) + (t + (list type))))))) + rows))) + + + + +#+mysql-client-v4.1 +(defmethod database-free-prepared ((stmt mysql-stmt)) + (with-slots (stmt) stmt + (mysql-stmt-close stmt)) + ) + + +;;; Database capabilities + +(defmethod db-type-use-column-on-drop-index? ((db-type (eql :mysql))) + t) + +(defmethod db-type-has-views? ((db-type (eql :mysql))) + #+mysql-client-v5 t + #-mysql-client-v5 nil) + +(defmethod db-type-has-subqueries? ((db-type (eql :mysql))) + #+(or mysql-client-v4.1 mysql-client-v5) t + #-(or mysql-client-v4.1 mysql-client-v5) nil) + +(defmethod db-type-has-boolean-where? ((db-type (eql :mysql))) + #+(or mysql-client-v4.1 mysql-client-v5) t + #-(or mysql-client-v4.1 mysql-client-v5) nil) + +(defmethod db-type-has-union? ((db-type (eql :mysql))) + (not (eql (schar mysql::*mysql-client-info* 0) #\3))) + +(defmethod db-type-transaction-capable? ((db-type (eql :mysql)) database) + (let ((tuple (car (database-query "SHOW VARIABLES LIKE 'HAVE_INNODB'" database :auto nil)))) + (and tuple (string-equal "YES" (second tuple))))) + +(defmethod db-type-has-prepared-stmt? ((db-type (eql :mysql))) + #+(or mysql-client-v4.1 mysql-client-v5) t + #-(or mysql-client-v4.1 mysql-client-v5) nil) + +(defmethod db-type-has-auto-increment? ((db-type (eql :mysql))) + t) + +(when (clsql-sys:database-type-library-loaded :mysql) + (clsql-sys:initialize-database-type :database-type :mysql)) diff --git a/db-mysql/testing/mysql-struct-size.cc b/db-mysql/testing/mysql-struct-size.cc new file mode 100644 index 0000000..464ef46 --- /dev/null +++ b/db-mysql/testing/mysql-struct-size.cc @@ -0,0 +1,10 @@ +#include +#include "/usr/include/mysql/mysql.h" + +int main (int argc, char** argv) +{ + printf ("Size of MYSQL struct: %ld\n", sizeof (MYSQL)); + printf("Sizeof MYSQL_BIND: %d\n", sizeof(MYSQL_BIND)); + printf("Sizeof MYSQL_FIELD: %d\n", sizeof(MYSQL_FIELD)); + printf("Sizeof MYSQL_TIME: %d\n", sizeof(MYSQL_TIME)); +} diff --git a/db-mysql/testing/mysql-struct-size.lisp b/db-mysql/testing/mysql-struct-size.lisp new file mode 100644 index 0000000..3aac6f9 --- /dev/null +++ b/db-mysql/testing/mysql-struct-size.lisp @@ -0,0 +1,11 @@ +(in-package :mysql) + +#+lispworks +(progn + (setq c (fli:allocate-foreign-object :type 'mysql-mysql)) + (format t "~&Size MYSQL structure: ~d" (fli:pointer-element-size c))) +#+allegro +(progn + (setq c (ff:allocate-fobject 'mysql-mysql :foreign)) + (format t "~&Size MYSQL structure: ~A" c)) + diff --git a/db-odbc/Makefile b/db-odbc/Makefile new file mode 100644 index 0000000..2497b4f --- /dev/null +++ b/db-odbc/Makefile @@ -0,0 +1,24 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL SQL interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean + diff --git a/db-odbc/odbc-api.lisp b/db-odbc/odbc-api.lisp new file mode 100644 index 0000000..208dafa --- /dev/null +++ b/db-odbc/odbc-api.lisp @@ -0,0 +1,1023 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: odbc -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-api.lisp +;;;; Purpose: Low-level ODBC API using UFFI +;;;; Authors: Kevin M. Rosenberg and Paul Meurer +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; and Copyright (C) Paul Meurer 1999 - 2001. All rights reserved. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:odbc) + +(defvar *null* nil + "Lisp representation of SQL Null value, default = nil. +May be locally bound to something else if a certain type is necessary.") + + +(defvar *binary-format* :unsigned-byte-vector) +(defvar *time-format* + (lambda (time) + (clsql-sys:format-time nil time :format :iso)) + "Bound to a function that converts from a clsql:wall-time to the desired + representation of date/time/timestamp. + By default, returns an iso-timestring.") + +(defvar +null-ptr+ (make-null-pointer :byte)) +(defparameter +null-handle-ptr+ (make-null-pointer :void)) +(defvar *info-output* nil + "Stream to send SUCCESS_WITH_INFO messages.") + +(defmacro %put-str (ptr string &optional max-length) + (let ((size (gensym))) + `(let ((,size (length ,string))) + (when (and ,max-length (> ,size ,max-length)) + (error 'clsql:sql-database-data-error + :message + (format nil "string \"~a\" of length ~d is longer than max-length: ~d" + ,string ,size ,max-length))) + (with-cast-pointer (char-ptr ,ptr :byte) + (dotimes (i ,size) + (setf (deref-array char-ptr '(:array :byte) i) + (char-code (char ,string i)))) + (setf (deref-array char-ptr '(:array :byte) ,size) 0))))) + +(defmacro with-allocate-foreign-string ((var len) &body body) + "Safely does uffi:allocate-foreign-string-- making sure we do the uffi:free-foreign-object" + `(let ((,var)) + (unwind-protect + (progn + (setf ,var (uffi:allocate-foreign-string ,len)) + ,@body) + (when ,var + (uffi:free-foreign-object ,var))))) + +(defmacro with-allocate-foreign-strings (bindings &rest body) + (if bindings + `(with-allocate-foreign-string ,(car bindings) + (with-allocate-foreign-strings ,(cdr bindings) + ,@body)) + `(progn ,@body))) + +(defun handle-error (henv hdbc hstmt) + (with-allocate-foreign-strings ((sql-state 256) + (error-message #.$SQL_MAX_MESSAGE_LENGTH)) + (with-foreign-objects ((error-code #.$ODBC-LONG-TYPE) + (msg-length :short)) + (SQLError henv hdbc hstmt sql-state + error-code error-message + #.$SQL_MAX_MESSAGE_LENGTH msg-length) + (values + (convert-from-foreign-string error-message) + (convert-from-foreign-string sql-state) + (deref-pointer msg-length :short) + (deref-pointer error-code #.$ODBC-LONG-TYPE))))) + +(defun sql-state (henv hdbc hstmt) + (with-allocate-foreign-strings ((sql-state 256) + (error-message #.$SQL_MAX_MESSAGE_LENGTH)) + (with-foreign-objects ((error-code #.$ODBC-LONG-TYPE) + (msg-length :short)) + (SQLError henv hdbc hstmt sql-state error-code + error-message #.$SQL_MAX_MESSAGE_LENGTH msg-length) + (convert-from-foreign-string sql-state) + ;; test this: return a keyword for efficiency + ;;(%cstring-to-keyword state) + ))) + +(defmacro with-error-handling ((&key henv hdbc hstmt (print-info t)) + odbc-call &body body) + (let ((result-code (gensym "RC-"))) + `(let ((,result-code ,odbc-call)) + + ;; Check for allegro v7 & v8 bug with ODBC calls returning + ;; 32-bit unsigned ints, not 16-bit signed ints + #+(and allegro mswindows) + (when (> ,result-code #xFFFF) + (warn (format nil "16-bit return bug: result-code #x~X for expression ~S" + ,result-code (quote ,odbc-call))) + (setq ,result-code (logand ,result-code #xFFFF)) + (when (> ,result-code #x7FFF) + (setq ,result-code (- ,result-code #x10000)))) + + (case ,result-code + (#.$SQL_SUCCESS + (progn ,result-code ,@body)) + (#.$SQL_SUCCESS_WITH_INFO + (when ,print-info + (multiple-value-bind (error-message sql-state) + (handle-error (or ,henv +null-handle-ptr+) + (or ,hdbc +null-handle-ptr+) + (or ,hstmt +null-handle-ptr+)) + (when *info-output* + (format *info-output* "[ODBC info ~A] ~A state: ~A" + ,result-code error-message + sql-state)))) + (progn ,result-code ,@body)) + (#.$SQL_INVALID_HANDLE + (error + 'clsql-sys:sql-database-error + :message "ODBC: Invalid handle")) + (#.$SQL_STILL_EXECUTING + (error + 'clsql-sys:sql-temporary-error + :message "ODBC: Still executing")) + (#.$SQL_ERROR + (multiple-value-bind (error-message sql-state) + (handle-error (or ,henv +null-handle-ptr+) + (or ,hdbc +null-handle-ptr+) + (or ,hstmt +null-handle-ptr+)) + (error + 'clsql-sys:sql-database-error + :message error-message + :secondary-error-id sql-state))) + (#.$SQL_NO_DATA_FOUND + (progn ,result-code ,@body)) + ;; work-around for Allegro 7.0beta AMD64 which returns negative numbers + (otherwise + (multiple-value-bind (error-message sql-state) + (handle-error (or ,henv +null-handle-ptr+) + (or ,hdbc +null-handle-ptr+) + (or ,hstmt +null-handle-ptr+)) + (error + 'clsql-sys:sql-database-error + :message error-message + :secondary-error-id sql-state)) + #+ignore + (progn ,result-code ,@body)))))) + +(defun %new-environment-handle () + (let ((henv + (with-foreign-object (phenv 'sql-handle) + (with-error-handling + () + (SQLAllocHandle $SQL_HANDLE_ENV +null-handle-ptr+ phenv) + (deref-pointer phenv 'sql-handle))))) + (%set-attr-odbc-version henv $SQL_OV_ODBC3) + henv)) + + +(defun %sql-free-environment (henv) + (with-error-handling + (:henv henv) + (SQLFreeEnv henv))) + +(defun %new-db-connection-handle (henv) + (with-foreign-object (phdbc 'sql-handle) + (setf (deref-pointer phdbc 'sql-handle) +null-handle-ptr+) + (with-error-handling + (:henv henv) + (SQLAllocHandle $SQL_HANDLE_DBC henv phdbc) + (deref-pointer phdbc 'sql-handle)))) + +(defun %free-statement (hstmt option) + (with-error-handling + (:hstmt hstmt) + (SQLFreeStmt + hstmt + (ecase option + (:drop $SQL_DROP) + (:close $SQL_CLOSE) + (:unbind $SQL_UNBIND) + (:reset $SQL_RESET_PARAMS))))) + +(defmacro with-statement-handle ((hstmt hdbc) &body body) + `(let ((,hstmt (%new-statement-handle ,hdbc))) + (unwind-protect + (progn ,@body) + (%free-statement ,hstmt :drop)))) + +;; functional interface + +(defun %sql-connect (hdbc server uid pwd) + (with-cstrings ((server-ptr server) + (uid-ptr uid) + (pwd-ptr pwd)) + (with-error-handling + (:hdbc hdbc) + (SQLConnect hdbc server-ptr $SQL_NTS uid-ptr + $SQL_NTS pwd-ptr $SQL_NTS)))) + +(defun %sql-driver-connect (hdbc connection-string completion window-handle) + (with-cstring (connection-ptr connection-string) + (with-allocate-foreign-string (completed-connection-string-ptr $SQL_MAX_CONN_OUT) + (with-foreign-object (completed-connection-length :short) + (with-error-handling + (:hdbc hdbc) + (SQLDriverConnect hdbc + (or window-handle + +null-handle-ptr+) + connection-ptr $SQL_NTS + completed-connection-string-ptr $SQL_MAX_CONN_OUT + completed-connection-length + completion)))))) + +(defun %disconnect (hdbc) + (with-error-handling + (:hdbc hdbc) + (SQLDisconnect hdbc) + (with-error-handling + (:hdbc hdbc) + (SQLFreeHandle $SQL_HANDLE_DBC hdbc)))) + +(defun %commit (henv hdbc) + (with-error-handling + (:henv henv :hdbc hdbc) + (SQLTransact + henv hdbc $SQL_COMMIT))) + +(defun %rollback (henv hdbc) + (with-error-handling + (:henv henv :hdbc hdbc) + (SQLTransact + henv hdbc $SQL_ROLLBACK))) + +; col-nr is zero-based in Lisp but 1 based in sql +; col-nr = :bookmark retrieves a bookmark. +(defun %bind-column (hstmt column-nr c-type data-ptr precision out-len-ptr) + (with-error-handling + (:hstmt hstmt) + (SQLBindCol hstmt + (if (eq column-nr :bookmark) 0 (1+ column-nr)) + c-type data-ptr precision out-len-ptr))) + +; parameter-nr is zero-based in Lisp +(defun %sql-bind-parameter (hstmt parameter-nr parameter-type c-type + sql-type precision scale data-ptr + max-value out-len-ptr) + (with-error-handling + (:hstmt hstmt) + (SQLBindParameter hstmt (1+ parameter-nr) + parameter-type ;$SQL_PARAM_INPUT + c-type ;$SQL_C_CHAR + sql-type ;$SQL_VARCHAR + precision ;(1- (length str)) + scale ;0 + data-ptr + max-value + out-len-ptr ;#.+null-ptr+ + ))) + +(defun %sql-fetch (hstmt) + (with-error-handling + (:hstmt hstmt) + (SQLFetch hstmt))) + +(defun %new-statement-handle (hdbc) + (let ((statement-handle + (with-foreign-object (phstmt 'sql-handle) + (with-error-handling + (:hdbc hdbc) + (SQLAllocHandle $SQL_HANDLE_STMT hdbc phstmt) + (deref-pointer phstmt 'sql-handle))))) + (if (uffi:null-pointer-p statement-handle) + (error 'clsql:sql-database-error :message "Received null statement handle.") + statement-handle))) + +(defun %sql-get-info (hdbc info-type) + (ecase info-type + ;; those return string + ((#.$SQL_ACCESSIBLE_PROCEDURES + #.$SQL_ACCESSIBLE_TABLES + #.$SQL_COLUMN_ALIAS + #.$SQL_DATA_SOURCE_NAME + #.$SQL_DATA_SOURCE_READ_ONLY + #.$SQL_DBMS_NAME + #.$SQL_DBMS_VER + #.$SQL_DRIVER_NAME + #.$SQL_DRIVER_ODBC_VER + #.$SQL_DRIVER_VER + #.$SQL_EXPRESSIONS_IN_ORDERBY + #.$SQL_IDENTIFIER_QUOTE_CHAR + #.$SQL_KEYWORDS + #.$SQL_LIKE_ESCAPE_CLAUSE + #.$SQL_MAX_ROW_SIZE_INCLUDES_LONG + #.$SQL_MULT_RESULT_SETS + #.$SQL_MULTIPLE_ACTIVE_TXN + #.$SQL_NEED_LONG_DATA_LEN + #.$SQL_ODBC_SQL_OPT_IEF + #.$SQL_ODBC_VER + #.$SQL_ORDER_BY_COLUMNS_IN_SELECT + #.$SQL_OUTER_JOINS + #.$SQL_OWNER_TERM + #.$SQL_PROCEDURE_TERM + #.$SQL_PROCEDURES + #.$SQL_QUALIFIER_NAME_SEPARATOR + #.$SQL_QUALIFIER_TERM + #.$SQL_ROW_UPDATES + #.$SQL_SEARCH_PATTERN_ESCAPE + #.$SQL_SERVER_NAME + #.$SQL_SPECIAL_CHARACTERS + #.$SQL_TABLE_TERM + #.$SQL_USER_NAME) + (with-allocate-foreign-string (info-ptr 1024) + (with-foreign-object (info-length-ptr :short) + (with-error-handling + (:hdbc hdbc) + (SQLGetInfo hdbc info-type info-ptr 1023 info-length-ptr) + (convert-from-foreign-string info-ptr))))) + ;; those returning a word + ((#.$SQL_ACTIVE_CONNECTIONS + #.$SQL_ACTIVE_STATEMENTS + #.$SQL_CONCAT_NULL_BEHAVIOR + #.$SQL_CORRELATION_NAME + #.$SQL_CURSOR_COMMIT_BEHAVIOR + #.$SQL_CURSOR_ROLLBACK_BEHAVIOR + #.$SQL_MAX_COLUMN_NAME_LEN + #.$SQL_MAX_COLUMNS_IN_GROUP_BY + #.$SQL_MAX_COLUMNS_IN_INDEX + #.$SQL_MAX_COLUMNS_IN_ORDER_BY + #.$SQL_MAX_COLUMNS_IN_SELECT + #.$SQL_MAX_COLUMNS_IN_TABLE + #.$SQL_MAX_CURSOR_NAME_LEN + #.$SQL_MAX_OWNER_NAME_LEN + #.$SQL_MAX_PROCEDURE_NAME_LEN + #.$SQL_MAX_QUALIFIER_NAME_LEN + #.$SQL_MAX_TABLE_NAME_LEN + #.$SQL_MAX_TABLES_IN_SELECT + #.$SQL_MAX_USER_NAME_LEN + #.$SQL_NON_NULLABLE_COLUMNS + #.$SQL_NULL_COLLATION + #.$SQL_ODBC_API_CONFORMANCE + #.$SQL_ODBC_SAG_CLI_CONFORMANCE + #.$SQL_ODBC_SQL_CONFORMANCE + #.$SQL_QUALIFIER_LOCATION + #.$SQL_QUOTED_IDENTIFIER_CASE + #.$SQL_TXN_CAPABLE) + (with-foreign-objects ((info-ptr :short) + (info-length-ptr :short)) + (with-error-handling + (:hdbc hdbc) + (SQLGetInfo hdbc + info-type + info-ptr + 255 + info-length-ptr) + (deref-pointer info-ptr :short))) + ) + ;; those returning a long bitmask + ((#.$SQL_ALTER_TABLE + #.$SQL_BOOKMARK_PERSISTENCE + #.$SQL_CONVERT_BIGINT + #.$SQL_CONVERT_BINARY + #.$SQL_CONVERT_BIT + #.$SQL_CONVERT_CHAR + #.$SQL_CONVERT_DATE + #.$SQL_CONVERT_DECIMAL + #.$SQL_CONVERT_DOUBLE + #.$SQL_CONVERT_FLOAT + #.$SQL_CONVERT_INTEGER + #.$SQL_CONVERT_LONGVARCHAR + #.$SQL_CONVERT_NUMERIC + #.$SQL_CONVERT_REAL + #.$SQL_CONVERT_SMALLINT + #.$SQL_CONVERT_TIME + #.$SQL_CONVERT_TIMESTAMP + #.$SQL_CONVERT_TINYINT + #.$SQL_CONVERT_VARBINARY + #.$SQL_CONVERT_VARCHAR + #.$SQL_CONVERT_LONGVARBINARY + #.$SQL_CONVERT_FUNCTIONS + #.$SQL_FETCH_DIRECTION + #.$SQL_FILE_USAGE + #.$SQL_GETDATA_EXTENSIONS + #.$SQL_LOCK_TYPES + #.$SQL_MAX_INDEX_SIZE + #.$SQL_MAX_ROW_SIZE + #.$SQL_MAX_STATEMENT_LEN + #.$SQL_NUMERIC_FUNCTIONS + #.$SQL_OWNER_USAGE + #.$SQL_POS_OPERATIONS + #.$SQL_POSITIONED_STATEMENTS + #.$SQL_QUALIFIER_USAGE + #.$SQL_SCROLL_CONCURRENCY + #.$SQL_SCROLL_OPTIONS + #.$SQL_STATIC_SENSITIVITY + #.$SQL_STRING_FUNCTIONS + #.$SQL_SUBQUERIES + #.$SQL_SYSTEM_FUNCTIONS + #.$SQL_TIMEDATE_ADD_INTERVALS + #.$SQL_TIMEDATE_DIFF_INTERVALS + #.$SQL_TIMEDATE_FUNCTIONS + #.$SQL_TXN_ISOLATION_OPTION + #.$SQL_UNION) + (with-foreign-objects ((info-ptr #.$ODBC-LONG-TYPE) + (info-length-ptr :short)) + (with-error-handling + (:hdbc hdbc) + (SQLGetInfo hdbc + info-type + info-ptr + 255 + info-length-ptr) + (deref-pointer info-ptr #.$ODBC-LONG-TYPE))) + ) + ;; those returning a long integer + ((#.$SQL_DEFAULT_TXN_ISOLATION + #.$SQL_DRIVER_HDBC + #.$SQL_DRIVER_HENV + #.$SQL_DRIVER_HLIB + #.$SQL_DRIVER_HSTMT + #.$SQL_GROUP_BY + #.$SQL_IDENTIFIER_CASE + #.$SQL_MAX_BINARY_LITERAL_LEN + #.$SQL_MAX_CHAR_LITERAL_LEN + #.$SQL_ACTIVE_ENVIRONMENTS + ) + (with-foreign-objects ((info-ptr #.$ODBC-LONG-TYPE) + (info-length-ptr :short)) + (with-error-handling + (:hdbc hdbc) + (SQLGetInfo hdbc info-type info-ptr 255 info-length-ptr) + (deref-pointer info-ptr #.$ODBC-LONG-TYPE)))))) + +(defun %sql-exec-direct (sql hstmt henv hdbc) + (with-cstring (sql-ptr sql) + (with-error-handling + (:hstmt hstmt :henv henv :hdbc hdbc) + (SQLExecDirect hstmt sql-ptr $SQL_NTS)))) + +(defun %sql-cancel (hstmt) + (with-error-handling + (:hstmt hstmt) + (SQLCancel hstmt))) + +(defun %sql-execute (hstmt) + (with-error-handling + (:hstmt hstmt) + (SQLExecute hstmt))) + +(defun result-columns-count (hstmt) + (with-foreign-objects ((columns-nr-ptr :short)) + (with-error-handling (:hstmt hstmt) + (SQLNumResultCols hstmt columns-nr-ptr) + (deref-pointer columns-nr-ptr :short)))) + +(defun result-rows-count (hstmt) + (with-foreign-objects ((row-count-ptr #.$ODBC-LONG-TYPE)) + (with-error-handling (:hstmt hstmt) + (SQLRowCount hstmt row-count-ptr) + (deref-pointer row-count-ptr #.$ODBC-LONG-TYPE)))) + +;; column counting is 1-based +(defun %describe-column (hstmt column-nr) + (with-allocate-foreign-string (column-name-ptr 256) + (with-foreign-objects ((column-name-length-ptr :short) + (column-sql-type-ptr :short) + (column-precision-ptr #.$ODBC-ULONG-TYPE) + (column-scale-ptr :short) + (column-nullable-p-ptr :short)) + (with-error-handling (:hstmt hstmt) + (SQLDescribeCol hstmt column-nr column-name-ptr 256 + column-name-length-ptr + column-sql-type-ptr + column-precision-ptr + column-scale-ptr + column-nullable-p-ptr) + (values + (convert-from-foreign-string column-name-ptr) + (deref-pointer column-sql-type-ptr :short) + (deref-pointer column-precision-ptr #.$ODBC-ULONG-TYPE) + (deref-pointer column-scale-ptr :short) + (deref-pointer column-nullable-p-ptr :short)))))) + +;; parameter counting is 1-based +;; this function isn't used, which is good because FreeTDS dosn't support it. +(defun %describe-parameter (hstmt parameter-nr) + (with-foreign-objects ((column-sql-type-ptr :short) + (column-precision-ptr #.$ODBC-ULONG-TYPE) + (column-scale-ptr :short) + (column-nullable-p-ptr :short)) + (with-error-handling + (:hstmt hstmt) + (SQLDescribeParam hstmt parameter-nr + column-sql-type-ptr + column-precision-ptr + column-scale-ptr + column-nullable-p-ptr) + (values + (deref-pointer column-sql-type-ptr :short) + (deref-pointer column-precision-ptr #.$ODBC-ULONG-TYPE) + (deref-pointer column-scale-ptr :short) + (deref-pointer column-nullable-p-ptr :short))))) + +(defun %column-attributes (hstmt column-nr descriptor-type) + (with-allocate-foreign-string (descriptor-info-ptr 256) + (with-foreign-objects ((descriptor-length-ptr :short) + (numeric-descriptor-ptr #.$ODBC-LONG-TYPE)) + (with-error-handling + (:hstmt hstmt) + (SQLColAttributes hstmt column-nr descriptor-type descriptor-info-ptr + 256 descriptor-length-ptr + numeric-descriptor-ptr) + (values + (convert-from-foreign-string descriptor-info-ptr) + (deref-pointer numeric-descriptor-ptr #.$ODBC-LONG-TYPE)))))) + +(defun %prepare-describe-columns (hstmt table-qualifier table-owner + table-name column-name) + (with-cstrings ((table-qualifier-ptr table-qualifier) + (table-owner-ptr table-owner) + (table-name-ptr table-name) + (column-name-ptr column-name)) + (with-error-handling + (:hstmt hstmt) + (SQLColumns hstmt + table-qualifier-ptr (length table-qualifier) + table-owner-ptr (length table-owner) + table-name-ptr (length table-name) + column-name-ptr (length column-name))))) + +(defun %describe-columns (hdbc table-qualifier table-owner + table-name column-name) + (with-statement-handle (hstmt hdbc) + (%prepare-describe-columns hstmt table-qualifier table-owner + table-name column-name) + (fetch-all-rows hstmt))) + +(defun %sql-data-sources (henv &key (direction :first)) + (with-allocate-foreign-strings ((name-ptr (1+ $SQL_MAX_DSN_LENGTH)) + (description-ptr 1024)) + (with-foreign-objects ((name-length-ptr :short) + (description-length-ptr :short)) + (let ((res (with-error-handling + (:henv henv) + (SQLDataSources henv + (ecase direction + (:first $SQL_FETCH_FIRST) + (:next $SQL_FETCH_NEXT)) + name-ptr + (1+ $SQL_MAX_DSN_LENGTH) + name-length-ptr + description-ptr + 1024 + description-length-ptr)))) + (when (= res $SQL_NO_DATA_FOUND) + (values + (convert-from-foreign-string name-ptr) + (convert-from-foreign-string description-ptr))))))) + + + +(defun sql-to-c-type (sql-type) + (ecase sql-type + ;; Added -10 for MSSQL ntext type and -11 for nvarchar + ((#.$SQL_CHAR #.$SQL_VARCHAR #.$SQL_LONGVARCHAR + #.$SQL_NUMERIC #.$sql_decimal -8 -9 -10 -11) $SQL_C_CHAR) + (#.$SQL_INTEGER $SQL_C_SLONG) + (#.$SQL_BIGINT $SQL_C_SBIGINT) + (#.$SQL_SMALLINT $SQL_C_SSHORT) + (#.$SQL_DOUBLE $SQL_C_DOUBLE) + (#.$SQL_FLOAT $SQL_C_DOUBLE) + (#.$SQL_REAL $SQL_C_FLOAT) + (#.$SQL_DATE $SQL_C_DATE) + (#.$SQL_TIME $SQL_C_TIME) + (#.$SQL_TIMESTAMP $SQL_C_TIMESTAMP) + (#.$SQL_TYPE_DATE $SQL_C_TYPE_DATE) + (#.$SQL_TYPE_TIME $SQL_C_TYPE_TIME) + (#.$SQL_TYPE_TIMESTAMP $SQL_C_TYPE_TIMESTAMP) + ((#.$SQL_BINARY #.$SQL_VARBINARY #.$SQL_LONGVARBINARY) $SQL_C_BINARY) + (#.$SQL_TINYINT $SQL_C_STINYINT) + (#.$SQL_BIT $SQL_C_BIT))) + +(def-type byte-pointer-type (* :byte)) +(def-type short-pointer-type (* :short)) +(def-type int-pointer-type (* :int)) +(def-type long-pointer-type (* #.$ODBC-LONG-TYPE)) +(def-type big-pointer-type (* #.$ODBC-BIG-TYPE)) +(def-type float-pointer-type (* :float)) +(def-type double-pointer-type (* :double)) +(def-type string-pointer-type (* :unsigned-char)) + +(defun get-cast-byte (ptr) + (locally (declare (type byte-pointer-type ptr)) + (deref-pointer ptr :byte))) + +(defun get-cast-short (ptr) + (locally (declare (type short-pointer-type ptr)) + (deref-pointer ptr :short))) + +(defun get-cast-int (ptr) + (locally (declare (type int-pointer-type ptr)) + (deref-pointer ptr :int))) + +(defun get-cast-long (ptr) + (locally (declare (type long-pointer-type ptr)) + (deref-pointer ptr #.$ODBC-LONG-TYPE))) + +(defun get-cast-big (ptr) + (locally (declare (type big-pointer-type ptr)) + (deref-pointer ptr #.$ODBC-BIG-TYPE))) + +(defun get-cast-single-float (ptr) + (locally (declare (type float-pointer-type ptr)) + (deref-pointer ptr :float))) + +(defun get-cast-double-float (ptr) + (locally (declare (type double-pointer-type ptr)) + (deref-pointer ptr :double))) + +(defun get-cast-foreign-string (ptr) + (locally (declare (type string-pointer-type ptr)) + (convert-from-foreign-string ptr))) + +(defun get-cast-binary (ptr len format) + "FORMAT is one of :unsigned-byte-vector, :bit-vector (:string, :hex-string)" + (with-cast-pointer (casted ptr :unsigned-byte) + (ecase format + (:unsigned-byte-vector + (let ((vector (make-array len :element-type '(unsigned-byte 8)))) + (dotimes (i len) + (setf (aref vector i) + (deref-array casted '(:array :unsigned-byte) i))) + vector)) + (:bit-vector + (let ((vector (make-array (ash len 3) :element-type 'bit))) + (dotimes (i len) + (let ((byte (deref-array casted '(:array :byte) i))) + (dotimes (j 8) + (setf (bit vector (+ (ash i 3) j)) + (logand (ash byte (- j 7)) 1))))) + vector))))) + + +(defun read-data (data-ptr c-type sql-type out-len-ptr result-type) + (declare (type long-ptr-type out-len-ptr)) + (let* ((out-len (get-cast-long out-len-ptr)) + (value + (cond ((= out-len $SQL_NULL_DATA) *null*) + (t + (case sql-type + ;; SQL extended datatypes + (#.$SQL_TINYINT (get-cast-byte data-ptr)) + (#.$SQL_C_STINYINT (get-cast-byte data-ptr)) ;; ? + (#.$SQL_C_SSHORT (get-cast-short data-ptr)) ;; ? + (#.$SQL_SMALLINT (get-cast-short data-ptr)) ;; ?? + (#.$SQL_INTEGER (get-cast-int data-ptr)) + (#.$SQL_BIGINT (get-cast-big data-ptr)) + ;; TODO: Change this to read in rationals instead of doubles + ((#.$SQL_DECIMAL #.$SQL_NUMERIC) + (let* ((*read-base* 10) + (*read-default-float-format* 'double-float) + (str (get-cast-foreign-string data-ptr))) + (read-from-string str))) + (#.$SQL_BIT (get-cast-byte data-ptr)) + (t + (case c-type + ((#.$SQL_C_DATE #.$SQL_C_TYPE_DATE) + (funcall *time-format* (date-to-clsql-time data-ptr))) + ((#.$SQL_C_TIME #.$SQL_C_TYPE_TIME) + (funcall *time-format* (time-to-clsql-time data-ptr))) + ((#.$SQL_C_TIMESTAMP #.$SQL_C_TYPE_TIMESTAMP) + (funcall *time-format* (timestamp-to-clsql-time data-ptr))) + (#.$SQL_INTEGER + (get-cast-int data-ptr)) + (#.$SQL_C_FLOAT + (get-cast-single-float data-ptr)) + (#.$SQL_C_DOUBLE + (get-cast-double-float data-ptr)) + (#.$SQL_C_SLONG + (get-cast-long data-ptr)) + #+lispworks + (#.$SQL_C_BIT ; encountered only in Access + (get-cast-byte data-ptr)) + (#.$SQL_C_BINARY + (get-cast-binary data-ptr out-len *binary-format*)) + ((#.$SQL_C_SSHORT #.$SQL_C_STINYINT) ; LMH short ints + (get-cast-short data-ptr)) ; LMH + (#.$SQL_C_SBIGINT (get-cast-big data-ptr)) + #+ignore + (#.$SQL_C_CHAR + (code-char (get-cast-short data-ptr))) + (t + (get-cast-foreign-string data-ptr))))))))) + + ;; FIXME: this could be better optimized for types which use READ-FROM-STRING above + + (if (and (or (eq result-type t) (eq result-type :string)) + value + (not (stringp value))) + (write-to-string value) + value))) + +;; which value is appropriate? +(defparameter +max-precision+ 4001) + +(defvar *break-on-unknown-data-type* t) + +;; C. Stacy's idea to factor this out +;; "Make it easy to add new datatypes by making new subroutine %ALLOCATE-BINDINGS, +;; so that I don't have to remember to make changes in more than one place. +;; Just keep it in synch with READ-DATA." +(defun %allocate-bindings (sql-type precision) + (let* ((c-type (sql-to-c-type sql-type)) + (size (if (zerop precision) + +max-precision+ ;; if the precision cannot be determined + (min precision +max-precision+))) + (long-p (= size +max-precision+)) + (data-ptr + (case c-type ;; add more? + (#.$SQL_C_SLONG (uffi:allocate-foreign-object #.$ODBC-LONG-TYPE)) + ((#.$SQL_C_DATE #.$SQL_C_TYPE_DATE) (allocate-foreign-object 'sql-c-date)) + ((#.$SQL_C_TIME #.$SQL_C_TYPE_TIME) (allocate-foreign-object 'sql-c-time)) + ((#.$SQL_C_TIMESTAMP #.$SQL_C_TYPE_TIMESTAMP) (allocate-foreign-object 'sql-c-timestamp)) + (#.$SQL_C_FLOAT (uffi:allocate-foreign-object :float)) + (#.$SQL_C_DOUBLE (uffi:allocate-foreign-object :double)) + (#.$SQL_C_BIT (uffi:allocate-foreign-object :byte)) + (#.$SQL_C_STINYINT (uffi:allocate-foreign-object :byte)) + (#.$SQL_C_SBIGINT (uffi:allocate-foreign-object #.$ODBC-BIG-TYPE)) + (#.$SQL_C_SSHORT (uffi:allocate-foreign-object :short)) + (#.$SQL_C_CHAR (uffi:allocate-foreign-string (1+ size))) + (#.$SQL_C_BINARY (uffi:allocate-foreign-string (1+ (* 2 size)))) + (t + ;; Maybe should signal a restartable condition for this? + (when *break-on-unknown-data-type* + (break "SQL type is ~A, precision ~D, size ~D, C type is ~A" + sql-type precision size c-type)) + (uffi:allocate-foreign-object :byte (1+ size))))) + (out-len-ptr (uffi:allocate-foreign-object #.$ODBC-LONG-TYPE))) + (values c-type data-ptr out-len-ptr size long-p))) + +(defun fetch-all-rows (hstmt &key free-option flatp) + (let ((column-count (result-columns-count hstmt))) + (unless (zerop column-count) + (let ((names (make-array column-count)) + (sql-types (make-array column-count :element-type 'fixnum)) + (c-types (make-array column-count :element-type 'fixnum)) + (precisions (make-array column-count :element-type 'fixnum)) + (data-ptrs (make-array column-count :initial-element nil)) + (out-len-ptrs (make-array column-count :initial-element nil)) + (scales (make-array column-count :element-type 'fixnum)) + (nullables-p (make-array column-count :element-type 'fixnum))) + (unwind-protect + (values + (progn + (dotimes (col-nr column-count) + ;; get column information + (multiple-value-bind (name sql-type precision scale nullable-p) + (%describe-column hstmt (1+ col-nr)) + ;; allocate space to bind result rows to + (multiple-value-bind (c-type data-ptr out-len-ptr) + (%allocate-bindings sql-type precision) + (%bind-column hstmt col-nr c-type data-ptr (1+ precision) out-len-ptr) + (setf (svref names col-nr) name + (aref sql-types col-nr) sql-type + (aref c-types col-nr) (sql-to-c-type sql-type) + (aref precisions col-nr) (if (zerop precision) 0 precision) + (aref scales col-nr) scale + (aref nullables-p col-nr) nullable-p + (aref data-ptrs col-nr) data-ptr + (aref out-len-ptrs col-nr) out-len-ptr)))) + ;; the main loop + (prog1 + (cond (flatp + (when (> column-count 1) + (error 'clsql:sql-database-error + :message "If more than one column is to be fetched, flatp has to be nil.")) + (loop until (= (%sql-fetch hstmt) $SQL_NO_DATA_FOUND) + collect + (read-data (aref data-ptrs 0) + (aref c-types 0) + (aref sql-types 0) + (aref out-len-ptrs 0) + t))) + (t + (loop until (= (%sql-fetch hstmt) $SQL_NO_DATA_FOUND) + collect + (loop for col-nr from 0 to (1- column-count) + collect + (read-data (aref data-ptrs col-nr) + (aref c-types col-nr) + (aref sql-types col-nr) + (aref out-len-ptrs col-nr) + t))))))) + names) + ;; dispose of memory etc + (when free-option (%free-statement hstmt free-option)) + (dotimes (col-nr column-count) + (let ((data-ptr (aref data-ptrs col-nr)) + (out-len-ptr (aref out-len-ptrs col-nr))) + (when data-ptr (free-foreign-object data-ptr)) ; we *did* allocate them + (when out-len-ptr (free-foreign-object out-len-ptr))))))))) + +;; to do: factor out common parts, put the sceleton (the obligatory macro part) +;; of %do-fetch into sql package (has been done) + +(defun %sql-prepare (hstmt sql) + (with-cstring (sql-ptr sql) + (with-error-handling (:hstmt hstmt) + (SQLPrepare hstmt sql-ptr $SQL_NTS)))) + +;; depending on option, we return a long int or a string; string not implemented +(defun get-connection-option (hdbc option) + (with-foreign-object (param-ptr #.$ODBC-LONG-TYPE) + (with-error-handling (:hdbc hdbc) + (SQLGetConnectOption hdbc option param-ptr) + (deref-pointer param-ptr #.$ODBC-LONG-TYPE)))) + +(defun set-connection-option (hdbc option param) + (with-error-handling (:hdbc hdbc) + (SQLSetConnectOption hdbc option param))) + +(defun disable-autocommit (hdbc) + (set-connection-option hdbc $SQL_AUTOCOMMIT $SQL_AUTOCOMMIT_OFF)) + +(defun enable-autocommit (hdbc) + (set-connection-option hdbc $SQL_AUTOCOMMIT $SQL_AUTOCOMMIT_ON)) + +(defun %sql-set-pos (hstmt row option lock) + (with-error-handling + (:hstmt hstmt) + (SQLSetPos hstmt row option lock))) + +(defun %sql-extended-fetch (hstmt fetch-type row) + (with-foreign-objects ((row-count-ptr #.$ODBC-ULONG-TYPE) + (row-status-ptr :short)) + (with-error-handling (:hstmt hstmt) + (SQLExtendedFetch hstmt fetch-type row row-count-ptr + row-status-ptr) + (values (deref-pointer row-count-ptr #.$ODBC-ULONG-TYPE) + (deref-pointer row-status-ptr :short))))) + +; column-nr is zero-based +(defun %sql-get-data (hstmt column-nr c-type data-ptr precision out-len-ptr) + (with-error-handling + (:hstmt hstmt :print-info nil) + (SQLGetData hstmt (if (eq column-nr :bookmark) 0 (1+ column-nr)) + c-type data-ptr precision out-len-ptr))) + +(defun %sql-param-data (hstmt param-ptr) + (with-error-handling (:hstmt hstmt :print-info t) ;; nil + (SQLParamData hstmt param-ptr))) + +(defun %sql-put-data (hstmt data-ptr size) + (with-error-handling + (:hstmt hstmt :print-info t) ;; nil + (SQLPutData hstmt data-ptr size))) + +(defconstant $sql-data-truncated (intern "01004" :keyword)) + + +(defun read-data-in-chunks (hstmt column-nr data-ptr c-type sql-type + out-len-ptr result-type) + (declare (type long-ptr-type out-len-ptr) + (ignore result-type)) + + (let* ((res (%sql-get-data hstmt column-nr c-type data-ptr + +max-precision+ out-len-ptr)) + (out-len (get-cast-long out-len-ptr)) + (result (if (equal out-len #.$SQL_NULL_DATA) + (return-from read-data-in-chunks *null*) + + ;;this isn't the most efficient way of doing it: + ;;the foreign string gets copied to lisp, then + ;;that is copied into the final string. However, + ;;the previous impl that tried to copy one + ;;character over at a time failed miserably on + ;;multibyte characters. + ;; + ;;In the face of multibyte characters, the out-len + ;;tells us the length in bytes but that doesn't + ;;particularly help us here in allocating a lisp + ;;array. So our best strategy is to just let the + ;;foreign library that's already dealing with + ;;encodings do its thing. + + (with-output-to-string (str) + (loop do (if (= c-type #.$SQL_CHAR) + (write-sequence (get-cast-foreign-string data-ptr) str) + (error 'clsql:sql-database-error + :message "wrong type. preliminary.")) + while (and (= res $SQL_SUCCESS_WITH_INFO) + (equal (sql-state +null-handle-ptr+ +null-handle-ptr+ hstmt) + "01004")) + do (setf res (%sql-get-data hstmt column-nr c-type data-ptr + +max-precision+ out-len-ptr))))))) + + ;; reset the out length for the next row + (setf (deref-pointer out-len-ptr #.$ODBC-LONG-TYPE) #.$SQL_NO_TOTAL) + (if (= sql-type $SQL_DECIMAL) + (let ((*read-base* 10)) + (read-from-string result)) + result))) + + +(def-type c-timestamp-ptr-type (* (:struct sql-c-timestamp))) +(def-type c-time-ptr-type (* (:struct sql-c-time))) +(def-type c-date-ptr-type (* (:struct sql-c-date))) + +(defun timestamp-to-clsql-time (ptr) + (declare (type c-timestamp-ptr-type ptr)) + (clsql-sys:make-time + :second (get-slot-value ptr 'sql-c-timestamp 'second) + :minute (get-slot-value ptr 'sql-c-timestamp 'minute) + :hour (get-slot-value ptr 'sql-c-timestamp 'hour) + :day (get-slot-value ptr 'sql-c-timestamp 'day) + :month (get-slot-value ptr 'sql-c-timestamp 'month) + :year (get-slot-value ptr 'sql-c-timestamp 'year) + :usec (let ((frac (get-slot-value ptr 'sql-c-timestamp 'fraction))) + (if frac (/ frac 1000) 0)))) + +(defun universal-time-to-timestamp (time &optional (fraction 0)) + "TODO: Dead function?" + (multiple-value-bind (sec min hour day month year) + (decode-universal-time time) + (let ((ptr (allocate-foreign-object 'sql-c-timestamp))) + (setf (get-slot-value ptr 'sql-c-timestamp 'second) sec + (get-slot-value ptr 'sql-c-timestamp 'minute) min + (get-slot-value ptr 'sql-c-timestamp 'hour) hour + (get-slot-value ptr 'sql-c-timestamp 'day) day + (get-slot-value ptr 'sql-c-timestamp 'month) month + (get-slot-value ptr 'sql-c-timestamp 'year) year + (get-slot-value ptr 'sql-c-timestamp 'fraction) fraction) + ptr))) + +(defun %put-timestamp (ptr time &optional (fraction 0)) + "TODO: Dead function?" + (declare (type c-timestamp-ptr-type ptr)) + (multiple-value-bind (sec min hour day month year) + (decode-universal-time time) + (setf (get-slot-value ptr 'sql-c-timestamp 'second) sec + (get-slot-value ptr 'sql-c-timestamp 'minute) min + (get-slot-value ptr 'sql-c-timestamp 'hour) hour + (get-slot-value ptr 'sql-c-timestamp 'day) day + (get-slot-value ptr 'sql-c-timestamp 'month) month + (get-slot-value ptr 'sql-c-timestamp 'year) year + (get-slot-value ptr 'sql-c-timestamp 'fraction) fraction) + ptr)) + +(defun date-to-clsql-time (ptr) + (declare (type c-date-ptr-type ptr)) + (clsql-sys:make-time + :second 0 :minute 0 :hour 0 + :day (get-slot-value ptr 'sql-c-timestamp 'day) + :month (get-slot-value ptr 'sql-c-timestamp 'month) + :year (get-slot-value ptr 'sql-c-timestamp 'year))) + +(defun time-to-clsql-time (ptr) + (declare (type c-time-ptr-type ptr)) + (clsql-sys:make-time + :second (get-slot-value ptr 'sql-c-timestamp 'second) + :minute (get-slot-value ptr 'sql-c-timestamp 'minute) + :hour (get-slot-value ptr 'sql-c-timestamp 'hour))) + + +;;; Added by KMR + +(defun %set-attr-odbc-version (henv version) + (with-error-handling (:henv henv) + ;;note that we are passing version as an integer that happens to be + ;;stuffed into a pointer. + ;;http://msdn.microsoft.com/en-us/library/ms709285%28v=VS.85%29.aspx + (SQLSetEnvAttr henv $SQL_ATTR_ODBC_VERSION + (make-pointer version :void) 0))) + +(defun %list-tables (hstmt) + (with-error-handling (:hstmt hstmt) + (SQLTables hstmt +null-ptr+ 0 +null-ptr+ 0 +null-ptr+ 0 +null-ptr+ 0))) + +(defun %table-statistics (table hstmt &key unique (ensure t) + &aux (table (princ-to-string + (clsql-sys::unescaped-database-identifier table)))) + (with-cstrings ((table-cs table)) + (with-error-handling (:hstmt hstmt) + (SQLStatistics + hstmt + +null-ptr+ 0 + +null-ptr+ 0 + table-cs $SQL_NTS + (if unique $SQL_INDEX_UNIQUE $SQL_INDEX_ALL) + (if ensure $SQL_ENSURE $SQL_QUICK))))) + +(defun %list-data-sources (henv) + (let ((results nil)) + (with-foreign-strings ((dsn-ptr (1+ $SQL_MAX_DSN_LENGTH)) + (desc-ptr 256)) + (with-foreign-objects ((dsn-len :short) + (desc-len :short)) + (let ((res (with-error-handling (:henv henv) + (SQLDataSources henv $SQL_FETCH_FIRST dsn-ptr + (1+ $SQL_MAX_DSN_LENGTH) + dsn-len desc-ptr 256 desc-len)))) + (when (or (eql res $SQL_SUCCESS) + (eql res $SQL_SUCCESS_WITH_INFO)) + (push (convert-from-foreign-string dsn-ptr) results)) + + (do ((res (with-error-handling (:henv henv) + (SQLDataSources henv $SQL_FETCH_NEXT dsn-ptr + (1+ $SQL_MAX_DSN_LENGTH) + dsn-len desc-ptr 256 desc-len)) + (with-error-handling (:henv henv) + (SQLDataSources henv $SQL_FETCH_NEXT dsn-ptr + (1+ $SQL_MAX_DSN_LENGTH) + dsn-len desc-ptr 256 desc-len)))) + ((not (or (eql res $SQL_SUCCESS) + (eql res $SQL_SUCCESS_WITH_INFO)))) + (push (convert-from-foreign-string dsn-ptr) results))))) + (nreverse results))) diff --git a/db-odbc/odbc-constants.lisp b/db-odbc/odbc-constants.lisp new file mode 100644 index 0000000..689fc12 --- /dev/null +++ b/db-odbc/odbc-constants.lisp @@ -0,0 +1,979 @@ + ;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: odbc -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-constants.lisp +;;;; Purpose: Constants for UFFI interface to ODBC +;;;; Authors: Kevin M. Rosenberg and Paul Meurer +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; and Copyright (C) Paul Meurer 1999 - 2001. All rights reserved. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:odbc) + +;; on SuSE AMD64 9.0, unixODBC is compiled with with SQLLEN being 4 bytes long +(defconstant $ODBC-LONG-TYPE :int) +(defconstant $ODBC-ULONG-TYPE :unsigned-int) +(defconstant $ODBC-BIG-TYPE :long-long) + +;; (defconstant $ODBCVER #x0210) + + +;; for new SQLAllocHandle functiion +(defconstant $SQL_HANDLE_ENV 1) +(defconstant $SQL_HANDLE_DBC 2) +(defconstant $SQL_HANDLE_STMT 3) +(defconstant $SQL_HANDLE_DESC 4) + +;; generally useful constants +(defconstant $SQL_SPEC_MAJOR 2) ;; Major version of specification +(defconstant $SQL_SPEC_MINOR 10) ;; Minor version of specification +(defvar $SQL_SPEC_STRING "02.10") ;; String constant for version +(defconstant $SQL_SQLSTATE_SIZE 5) ;; size of SQLSTATE +(defconstant $SQL_MAX_MESSAGE_LENGTH 512) ;; message buffer size +(defconstant $SQL_MAX_DSN_LENGTH 32) ;; maximum data source name size + +;; RETCODEs +(defconstant $SQL_INVALID_HANDLE -2) +(defconstant $SQL_ERROR -1) +(defconstant $SQL_SUCCESS 0) +(defconstant $SQL_SUCCESS_WITH_INFO 1) +(defconstant $SQL_NO_DATA_FOUND 100) + +;; Standard SQL datatypes, using ANSI type numbering +(defconstant $SQL_CHAR 1) +(defconstant $SQL_NUMERIC 2) +(defconstant $SQL_DECIMAL 3) +(defconstant $SQL_INTEGER 4) +(defconstant $SQL_SMALLINT 5) +(defconstant $SQL_FLOAT 6) +(defconstant $SQL_REAL 7) +(defconstant $SQL_DOUBLE 8) +(defconstant $SQL_VARCHAR 12) + +(defconstant $SQL_TYPE_MIN $SQL_CHAR) +(defconstant $SQL_TYPE_NULL 0) +(defconstant $SQL_TYPE_MAX $SQL_VARCHAR) + +;; C datatype to SQL datatype mapping SQL types + +(defconstant $SQL_C_CHAR $SQL_CHAR) ;; CHAR, VARCHAR, DECIMAL, NUMERIC +(defconstant $SQL_C_LONG $SQL_INTEGER) ;; INTEGER +(defconstant $SQL_C_SHORT $SQL_SMALLINT) ;; SMALLINT +(defconstant $SQL_C_FLOAT $SQL_REAL) ;; REAL +(defconstant $SQL_C_DOUBLE $SQL_DOUBLE) ;; FLOAT, DOUBLE +(defconstant $SQL_C_DEFAULT 99) + +;; NULL status constants. These are used in SQLColumns, SQLColAttributes, +;;SQLDescribeCol, SQLDescribeParam, and SQLSpecialColumns to describe the +;;nullablity of a column in a table. + +(defconstant $SQL_NO_NULLS 0) +(defconstant $SQL_NULLABLE 1) +(defconstant $SQL_NULLABLE_UNKNOWN 2) + +;; Special length values +(defconstant $SQL_NULL_DATA -1) +(defconstant $SQL_DATA_AT_EXEC -2) +(defconstant $SQL_NTS -3) + +;; SQLFreeStmt defines +(defconstant $SQL_CLOSE 0) +(defconstant $SQL_DROP 1) +(defconstant $SQL_UNBIND 2) +(defconstant $SQL_RESET_PARAMS 3) + +;; SQLTransact defines +(defconstant $SQL_COMMIT 0) +(defconstant $SQL_ROLLBACK 1) + +;; SQLColAttributes defines +(defconstant $SQL_COLUMN_COUNT 0) +(defconstant $SQL_COLUMN_NAME 1) +(defconstant $SQL_COLUMN_TYPE 2) +(defconstant $SQL_COLUMN_LENGTH 3) +(defconstant $SQL_COLUMN_PRECISION 4) +(defconstant $SQL_COLUMN_SCALE 5) +(defconstant $SQL_COLUMN_DISPLAY_SIZE 6) +(defconstant $SQL_COLUMN_NULLABLE 7) +(defconstant $SQL_COLUMN_UNSIGNED 8) +(defconstant $SQL_COLUMN_MONEY 9) +(defconstant $SQL_COLUMN_UPDATABLE 10) +(defconstant $SQL_COLUMN_AUTO_INCREMENT 11) +(defconstant $SQL_COLUMN_CASE_SENSITIVE 12) +(defconstant $SQL_COLUMN_SEARCHABLE 13) +(defconstant $SQL_COLUMN_TYPE_NAME 14) +(defconstant $SQL_COLUMN_TABLE_NAME 15) +(defconstant $SQL_COLUMN_OWNER_NAME 16) +(defconstant $SQL_COLUMN_QUALIFIER_NAME 17) +(defconstant $SQL_COLUMN_LABEL 18) +(defconstant $SQL_COLATT_OPT_MAX $SQL_COLUMN_LABEL) + +(defconstant $SQL_COLUMN_DRIVER_START 1000) + +(defconstant $SQL_COLATT_OPT_MIN $SQL_COLUMN_COUNT) + +;; SQLColAttributes subdefines for SQL_COLUMN_UPDATABLE +(defconstant $SQL_ATTR_READONLY 0) +(defconstant $SQL_ATTR_WRITE 1) +(defconstant $SQL_ATTR_READWRITE_UNKNOWN 2) + +;; SQLColAttributes subdefines for SQL_COLUMN_SEARCHABLE +;; These are also used by SQLGetInfo +(defconstant $SQL_UNSEARCHABLE 0) +(defconstant $SQL_LIKE_ONLY 1) +(defconstant $SQL_ALL_EXCEPT_LIKE 2) +(defconstant $SQL_SEARCHABLE 3) + +;; SQLError defines +(defconstant $SQL_NULL_HENV 0) +(defconstant $SQL_NULL_HDBC 0) +(defconstant $SQL_NULL_HSTMT 0) + +;; Defines for SQLGetFunctions +;; Core Functions +;; +(defconstant $SQL_API_SQLALLOCCONNECT 1) +(defconstant $SQL_API_SQLALLOCENV 2) +(defconstant $SQL_API_SQLALLOCSTMT 3) +(defconstant $SQL_API_SQLBINDCOL 4) +(defconstant $SQL_API_SQLCANCEL 5) +(defconstant $SQL_API_SQLCOLATTRIBUTES 6) +(defconstant $SQL_API_SQLCONNECT 7) +(defconstant $SQL_API_SQLDESCRIBECOL 8) +(defconstant $SQL_API_SQLDISCONNECT 9) +(defconstant $SQL_API_SQLERROR 10) +(defconstant $SQL_API_SQLEXECDIRECT 11) +(defconstant $SQL_API_SQLEXECUTE 12) +(defconstant $SQL_API_SQLFETCH 13) +(defconstant $SQL_API_SQLFREECONNECT 14) +(defconstant $SQL_API_SQLFREEENV 15) +(defconstant $SQL_API_SQLFREESTMT 16) +(defconstant $SQL_API_SQLGETCURSORNAME 17) +(defconstant $SQL_API_SQLNUMRESULTCOLS 18) +(defconstant $SQL_API_SQLPREPARE 19) +(defconstant $SQL_API_SQLROWCOUNT 20) +(defconstant $SQL_API_SQLSETCURSORNAME 21) +(defconstant $SQL_API_SQLSETPARAM 22) +(defconstant $SQL_API_SQLTRANSACT 23) +(defconstant $SQL_NUM_FUNCTIONS 23) +(defconstant $SQL_EXT_API_START 40) + +;; Level 1 Functions + +(defconstant $SQL_API_SQLCOLUMNS 40) +(defconstant $SQL_API_SQLDRIVERCONNECT 41) +(defconstant $SQL_API_SQLGETCONNECTOPTION 42) +(defconstant $SQL_API_SQLGETDATA 43) +(defconstant $SQL_API_SQLGETFUNCTIONS 44) +(defconstant $SQL_API_SQLGETINFO 45) +(defconstant $SQL_API_SQLGETSTMTOPTION 46) +(defconstant $SQL_API_SQLGETTYPEINFO 47) +(defconstant $SQL_API_SQLPARAMDATA 48) +(defconstant $SQL_API_SQLPUTDATA 49) +(defconstant $SQL_API_SQLSETCONNECTOPTION 50) +(defconstant $SQL_API_SQLSETSTMTOPTION 51) +(defconstant $SQL_API_SQLSPECIALCOLUMNS 52) +(defconstant $SQL_API_SQLSTATISTICS 53) +(defconstant $SQL_API_SQLTABLES 54) + +;; Level 2 Functions + +(defconstant $SQL_API_SQLBROWSECONNECT 55) +(defconstant $SQL_API_SQLCOLUMNPRIVILEGES 56) +(defconstant $SQL_API_SQLDATASOURCES 57) +(defconstant $SQL_API_SQLDESCRIBEPARAM 58) +(defconstant $SQL_API_SQLEXTENDEDFETCH 59) +(defconstant $SQL_API_SQLFOREIGNKEYS 60) +(defconstant $SQL_API_SQLMORERESULTS 61) +(defconstant $SQL_API_SQLNATIVESQL 62) +(defconstant $SQL_API_SQLNUMPARAMS 63) +(defconstant $SQL_API_SQLPARAMOPTIONS 64) +(defconstant $SQL_API_SQLPRIMARYKEYS 65) +(defconstant $SQL_API_SQLPROCEDURECOLUMNS 66) +(defconstant $SQL_API_SQLPROCEDURES 67) +(defconstant $SQL_API_SQLSETPOS 68) +(defconstant $SQL_API_SQLSETSCROLLOPTIONS 69) +(defconstant $SQL_API_SQLTABLEPRIVILEGES 70) + +;/* SDK 2.0 Additions */ +(defconstant $SQL_API_SQLDRIVERS 71) +(defconstant $SQL_API_SQLBINDPARAMETER 72) +(defconstant $SQL_EXT_API_LAST $SQL_API_SQLBINDPARAMETER) + +(defconstant $SQL_API_ALL_FUNCTIONS 0) + +(defconstant $SQL_NUM_EXTENSIONS (- $SQL_EXT_API_LAST $SQL_EXT_API_START -1)) +(defconstant $SQL_API_LOADBYORDINAL 199) + +;;; Defines for SQLGetInfo +(defconstant $SQL_INFO_FIRST 0) +(defconstant $SQL_ACTIVE_CONNECTIONS 0) +(defconstant $SQL_ACTIVE_STATEMENTS 1) +(defconstant $SQL_DATA_SOURCE_NAME 2) +(defconstant $SQL_DRIVER_HDBC 3) +(defconstant $SQL_DRIVER_HENV 4) +(defconstant $SQL_DRIVER_HSTMT 5) +(defconstant $SQL_DRIVER_NAME 6) +(defconstant $SQL_DRIVER_VER 7) +(defconstant $SQL_FETCH_DIRECTION 8) +(defconstant $SQL_ODBC_API_CONFORMANCE 9) +(defconstant $SQL_ODBC_VER 10) +(defconstant $SQL_ROW_UPDATES 11) +(defconstant $SQL_ODBC_SAG_CLI_CONFORMANCE 12) +(defconstant $SQL_SERVER_NAME 13) +(defconstant $SQL_SEARCH_PATTERN_ESCAPE 14) +(defconstant $SQL_ODBC_SQL_CONFORMANCE 15) + +(defconstant $SQL_DBMS_NAME 17) +(defconstant $SQL_DBMS_VER 18) + +(defconstant $SQL_ACCESSIBLE_TABLES 19) +(defconstant $SQL_ACCESSIBLE_PROCEDURES 20) +(defconstant $SQL_PROCEDURES 21) +(defconstant $SQL_CONCAT_NULL_BEHAVIOR 22) +(defconstant $SQL_CURSOR_COMMIT_BEHAVIOR 23) +(defconstant $SQL_CURSOR_ROLLBACK_BEHAVIOR 24) +(defconstant $SQL_DATA_SOURCE_READ_ONLY 25) +(defconstant $SQL_DEFAULT_TXN_ISOLATION 26) +(defconstant $SQL_EXPRESSIONS_IN_ORDERBY 27) +(defconstant $SQL_IDENTIFIER_CASE 28) +(defconstant $SQL_IDENTIFIER_QUOTE_CHAR 29) +(defconstant $SQL_MAX_COLUMN_NAME_LEN 30) +(defconstant $SQL_MAX_CURSOR_NAME_LEN 31) +(defconstant $SQL_MAX_OWNER_NAME_LEN 32) +(defconstant $SQL_MAX_PROCEDURE_NAME_LEN 33) +(defconstant $SQL_MAX_QUALIFIER_NAME_LEN 34) +(defconstant $SQL_MAX_TABLE_NAME_LEN 35) +(defconstant $SQL_MULT_RESULT_SETS 36) +(defconstant $SQL_MULTIPLE_ACTIVE_TXN 37) +(defconstant $SQL_OUTER_JOINS 38) +(defconstant $SQL_OWNER_TERM 39) +(defconstant $SQL_PROCEDURE_TERM 40) +(defconstant $SQL_QUALIFIER_NAME_SEPARATOR 41) +(defconstant $SQL_QUALIFIER_TERM 42) +(defconstant $SQL_SCROLL_CONCURRENCY 43) +(defconstant $SQL_SCROLL_OPTIONS 44) +(defconstant $SQL_TABLE_TERM 45) +(defconstant $SQL_TXN_CAPABLE 46) +(defconstant $SQL_USER_NAME 47) + +(defconstant $SQL_CONVERT_FUNCTIONS 48) +(defconstant $SQL_NUMERIC_FUNCTIONS 49) +(defconstant $SQL_STRING_FUNCTIONS 50) +(defconstant $SQL_SYSTEM_FUNCTIONS 51) +(defconstant $SQL_TIMEDATE_FUNCTIONS 52) + +(defconstant $SQL_CONVERT_BIGINT 53) +(defconstant $SQL_CONVERT_BINARY 54) +(defconstant $SQL_CONVERT_BIT 55) +(defconstant $SQL_CONVERT_CHAR 56) +(defconstant $SQL_CONVERT_DATE 57) +(defconstant $SQL_CONVERT_DECIMAL 58) +(defconstant $SQL_CONVERT_DOUBLE 59) +(defconstant $SQL_CONVERT_FLOAT 60) +(defconstant $SQL_CONVERT_INTEGER 61) +(defconstant $SQL_CONVERT_LONGVARCHAR 62) +(defconstant $SQL_CONVERT_NUMERIC 63) +(defconstant $SQL_CONVERT_REAL 64) +(defconstant $SQL_CONVERT_SMALLINT 65) +(defconstant $SQL_CONVERT_TIME 66) +(defconstant $SQL_CONVERT_TIMESTAMP 67) +(defconstant $SQL_CONVERT_TINYINT 68) +(defconstant $SQL_CONVERT_VARBINARY 69) +(defconstant $SQL_CONVERT_VARCHAR 70) +(defconstant $SQL_CONVERT_LONGVARBINARY 71) + +(defconstant $SQL_TXN_ISOLATION_OPTION 72) +(defconstant $SQL_ODBC_SQL_OPT_IEF 73) + +;;; ODBC SDK 1.0 Additions +(defconstant $SQL_CORRELATION_NAME 74) +(defconstant $SQL_NON_NULLABLE_COLUMNS 75) + +;;; ODBC SDK 2.0 Additions +(defconstant $SQL_DRIVER_HLIB 76) +(defconstant $SQL_DRIVER_ODBC_VER 77) +(defconstant $SQL_LOCK_TYPES 78) +(defconstant $SQL_POS_OPERATIONS 79) +(defconstant $SQL_POSITIONED_STATEMENTS 80) +(defconstant $SQL_GETDATA_EXTENSIONS 81) +(defconstant $SQL_BOOKMARK_PERSISTENCE 82) +(defconstant $SQL_STATIC_SENSITIVITY 83) +(defconstant $SQL_FILE_USAGE 84) +(defconstant $SQL_NULL_COLLATION 85) +(defconstant $SQL_ALTER_TABLE 86) +(defconstant $SQL_COLUMN_ALIAS 87) +(defconstant $SQL_GROUP_BY 88) +(defconstant $SQL_KEYWORDS 89) +(defconstant $SQL_ORDER_BY_COLUMNS_IN_SELECT 90) +(defconstant $SQL_OWNER_USAGE 91) +(defconstant $SQL_QUALIFIER_USAGE 92) +(defconstant $SQL_QUOTED_IDENTIFIER_CASE 93) +(defconstant $SQL_SPECIAL_CHARACTERS 94) +(defconstant $SQL_SUBQUERIES 95) +(defconstant $SQL_UNION 96) +(defconstant $SQL_MAX_COLUMNS_IN_GROUP_BY 97) +(defconstant $SQL_MAX_COLUMNS_IN_INDEX 98) +(defconstant $SQL_MAX_COLUMNS_IN_ORDER_BY 99) +(defconstant $SQL_MAX_COLUMNS_IN_SELECT 100) +(defconstant $SQL_MAX_COLUMNS_IN_TABLE 101) +(defconstant $SQL_MAX_INDEX_SIZE 102) +(defconstant $SQL_MAX_ROW_SIZE_INCLUDES_LONG 103) +(defconstant $SQL_MAX_ROW_SIZE 104) +(defconstant $SQL_MAX_STATEMENT_LEN 105) +(defconstant $SQL_MAX_TABLES_IN_SELECT 106) +(defconstant $SQL_MAX_USER_NAME_LEN 107) +(defconstant $SQL_MAX_CHAR_LITERAL_LEN 108) +(defconstant $SQL_TIMEDATE_ADD_INTERVALS 109) +(defconstant $SQL_TIMEDATE_DIFF_INTERVALS 110) +(defconstant $SQL_NEED_LONG_DATA_LEN 111) +(defconstant $SQL_MAX_BINARY_LITERAL_LEN 112) +(defconstant $SQL_LIKE_ESCAPE_CLAUSE 113) +(defconstant $SQL_QUALIFIER_LOCATION 114) +(defconstant $SQL_ACTIVE_ENVIRONMENTS 116) + +#| + +/*** ODBC SDK 2.01 Additions ***/) +(defconstant $SQL_OJ_CAPABILITIES 65003 ;; Temp value until ODBC 3.0 + +(defconstant $SQL_INFO_LAST SQL_QUALIFIER_LOCATION +) +(defconstant $SQL_INFO_DRIVER_START 1000 + +;; SQL_CONVERT_* return value bitmasks +) +(defconstant $SQL_CVT_CHAR #x00000001L) +(defconstant $SQL_CVT_NUMERIC #x00000002L) +(defconstant $SQL_CVT_DECIMAL #x00000004L) +(defconstant $SQL_CVT_INTEGER #x00000008L) +(defconstant $SQL_CVT_SMALLINT #x00000010L) +(defconstant $SQL_CVT_FLOAT #x00000020L) +(defconstant $SQL_CVT_REAL #x00000040L) +(defconstant $SQL_CVT_DOUBLE #x00000080L) +(defconstant $SQL_CVT_VARCHAR #x00000100L) +(defconstant $SQL_CVT_LONGVARCHAR #x00000200L) +(defconstant $SQL_CVT_BINARY #x00000400L) +(defconstant $SQL_CVT_VARBINARY #x00000800L) +(defconstant $SQL_CVT_BIT #x00001000L) +(defconstant $SQL_CVT_TINYINT #x00002000L) +(defconstant $SQL_CVT_BIGINT #x00004000L) +(defconstant $SQL_CVT_DATE #x00008000L) +(defconstant $SQL_CVT_TIME #x00010000L) +(defconstant $SQL_CVT_TIMESTAMP #x00020000L) +(defconstant $SQL_CVT_LONGVARBINARY #x00040000L) + +;; SQL_CONVERT_FUNCTIONS functions) +(defconstant $SQL_FN_CVT_CONVERT #x00000001L) + +;; SQL_STRING_FUNCTIONS functions + +(defconstant $SQL_FN_STR_CONCAT #x00000001L) +(defconstant $SQL_FN_STR_INSERT #x00000002L) +(defconstant $SQL_FN_STR_LEFT #x00000004L) +(defconstant $SQL_FN_STR_LTRIM #x00000008L) +(defconstant $SQL_FN_STR_LENGTH #x00000010L) +(defconstant $SQL_FN_STR_LOCATE #x00000020L) +(defconstant $SQL_FN_STR_LCASE #x00000040L) +(defconstant $SQL_FN_STR_REPEAT #x00000080L) +(defconstant $SQL_FN_STR_REPLACE #x00000100L) +(defconstant $SQL_FN_STR_RIGHT #x00000200L) +(defconstant $SQL_FN_STR_RTRIM #x00000400L) +(defconstant $SQL_FN_STR_SUBSTRING #x00000800L) +(defconstant $SQL_FN_STR_UCASE #x00001000L) +(defconstant $SQL_FN_STR_ASCII #x00002000L) +(defconstant $SQL_FN_STR_CHAR #x00004000L +(defconstant $SQL_FN_STR_DIFFERENCE #x00008000L) +(defconstant $SQL_FN_STR_LOCATE_2 #x00010000L) +(defconstant $SQL_FN_STR_SOUNDEX #x00020000L) +(defconstant $SQL_FN_STR_SPACE #x00040000L + +;; SQL_NUMERIC_FUNCTIONS functions +) +(defconstant $SQL_FN_NUM_ABS #x00000001L) +(defconstant $SQL_FN_NUM_ACOS #x00000002L) +(defconstant $SQL_FN_NUM_ASIN #x00000004L) +(defconstant $SQL_FN_NUM_ATAN #x00000008L) +(defconstant $SQL_FN_NUM_ATAN2 #x00000010L) +(defconstant $SQL_FN_NUM_CEILING #x00000020L) +(defconstant $SQL_FN_NUM_COS #x00000040L) +(defconstant $SQL_FN_NUM_COT #x00000080L) +(defconstant $SQL_FN_NUM_EXP #x00000100L) +(defconstant $SQL_FN_NUM_FLOOR #x00000200L) +(defconstant $SQL_FN_NUM_LOG #x00000400L) +(defconstant $SQL_FN_NUM_MOD #x00000800L) +(defconstant $SQL_FN_NUM_SIGN #x00001000L) +(defconstant $SQL_FN_NUM_SIN #x00002000L) +(defconstant $SQL_FN_NUM_SQRT #x00004000L) +(defconstant $SQL_FN_NUM_TAN #x00008000L) +(defconstant $SQL_FN_NUM_PI #x00010000L) +(defconstant $SQL_FN_NUM_RAND #x00020000L +(defconstant $SQL_FN_NUM_DEGREES #x00040000L) +(defconstant $SQL_FN_NUM_LOG10 #x00080000L) +(defconstant $SQL_FN_NUM_POWER #x00100000L) +(defconstant $SQL_FN_NUM_RADIANS #x00200000L) +(defconstant $SQL_FN_NUM_ROUND #x00400000L) +(defconstant $SQL_FN_NUM_TRUNCATE #x00800000L + +;; SQL_TIMEDATE_FUNCTIONS functions +) +(defconstant $SQL_FN_TD_NOW #x00000001L) +(defconstant $SQL_FN_TD_CURDATE #x00000002L) +(defconstant $SQL_FN_TD_DAYOFMONTH #x00000004L) +(defconstant $SQL_FN_TD_DAYOFWEEK #x00000008L) +(defconstant $SQL_FN_TD_DAYOFYEAR #x00000010L) +(defconstant $SQL_FN_TD_MONTH #x00000020L) +(defconstant $SQL_FN_TD_QUARTER #x00000040L) +(defconstant $SQL_FN_TD_WEEK #x00000080L) +(defconstant $SQL_FN_TD_YEAR #x00000100L) +(defconstant $SQL_FN_TD_CURTIME #x00000200L) +(defconstant $SQL_FN_TD_HOUR #x00000400L) +(defconstant $SQL_FN_TD_MINUTE #x00000800L) +(defconstant $SQL_FN_TD_SECOND #x00001000L +(defconstant $SQL_FN_TD_TIMESTAMPADD #x00002000L) +(defconstant $SQL_FN_TD_TIMESTAMPDIFF #x00004000L) +(defconstant $SQL_FN_TD_DAYNAME #x00008000L) +(defconstant $SQL_FN_TD_MONTHNAME #x00010000L + +;; SQL_SYSTEM_FUNCTIONS functions +) +(defconstant $SQL_FN_SYS_USERNAME #x00000001L) +(defconstant $SQL_FN_SYS_DBNAME #x00000002L) +(defconstant $SQL_FN_SYS_IFNULL #x00000004L + +;; SQL_TIMEDATE_ADD_INTERVALS and SQL_TIMEDATE_DIFF_INTERVALS functions + +(defconstant $SQL_FN_TSI_FRAC_SECOND #x00000001L) +(defconstant $SQL_FN_TSI_SECOND #x00000002L) +(defconstant $SQL_FN_TSI_MINUTE #x00000004L) +(defconstant $SQL_FN_TSI_HOUR #x00000008L) +(defconstant $SQL_FN_TSI_DAY #x00000010L) +(defconstant $SQL_FN_TSI_WEEK #x00000020L) +(defconstant $SQL_FN_TSI_MONTH #x00000040L) +(defconstant $SQL_FN_TSI_QUARTER #x00000080L) +(defconstant $SQL_FN_TSI_YEAR #x00000100L + +;; SQL_ODBC_API_CONFORMANCE values +) +(defconstant $SQL_OAC_NONE #x0000) +(defconstant $SQL_OAC_LEVEL1 #x0001) +(defconstant $SQL_OAC_LEVEL2 #x0002 + +;; SQL_ODBC_SAG_CLI_CONFORMANCE values +) +(defconstant $SQL_OSCC_NOT_COMPLIANT #x0000) +(defconstant $SQL_OSCC_COMPLIANT #x0001 + +;; SQL_ODBC_SQL_CONFORMANCE values +) +(defconstant $SQL_OSC_MINIMUM #x0000) +(defconstant $SQL_OSC_CORE #x0001) +(defconstant $SQL_OSC_EXTENDED #x0002 + +;; SQL_CONCAT_NULL_BEHAVIOR values +) +(defconstant $SQL_CB_NULL #x0000) +(defconstant $SQL_CB_NON_NULL #x0001 + +;; SQL_CURSOR_COMMIT_BEHAVIOR and SQL_CURSOR_ROLLBACK_BEHAVIOR values +) +(defconstant $SQL_CB_DELETE #x0000) +(defconstant $SQL_CB_CLOSE #x0001) +(defconstant $SQL_CB_PRESERVE #x0002 + +;; SQL_IDENTIFIER_CASE values +) +(defconstant $SQL_IC_UPPER #x0001) +(defconstant $SQL_IC_LOWER #x0002) +(defconstant $SQL_IC_SENSITIVE #x0003) +(defconstant $SQL_IC_MIXED #x0004 + +;; SQL_TXN_CAPABLE values +|# + +(defconstant $SQL_TC_NONE 0) +(defconstant $SQL_TC_DML 1) +(defconstant $SQL_TC_ALL 2) + +(defconstant $SQL_TC_DDL_COMMIT 3) +(defconstant $SQL_TC_DDL_IGNORE 4) + +;; SQL_SCROLL_OPTIONS masks + + +(defconstant $SQL_SO_FORWARD_ONLY #x00000001) +(defconstant $SQL_SO_KEYSET_DRIVEN #x00000002) +(defconstant $SQL_SO_DYNAMIC #x00000004) +(defconstant $SQL_SO_MIXED #x00000008) +(defconstant $SQL_SO_STATIC #x00000010) + +;; SQL_SCROLL_CONCURRENCY masks + +(defconstant $SQL_SCCO_READ_ONLY #x00000001) +(defconstant $SQL_SCCO_LOCK #x00000002) +(defconstant $SQL_SCCO_OPT_ROWVER #x00000004) +(defconstant $SQL_SCCO_OPT_VALUES #x00000008) + +;; SQL_FETCH_DIRECTION masks + +(defconstant $SQL_FD_FETCH_NEXT #x00000001) +(defconstant $SQL_FD_FETCH_FIRST #x00000002) +(defconstant $SQL_FD_FETCH_LAST #x00000004) +(defconstant $SQL_FD_FETCH_PRIOR #x00000008) +(defconstant $SQL_FD_FETCH_ABSOLUTE #x00000010) +(defconstant $SQL_FD_FETCH_RELATIVE #x00000020) +(defconstant $SQL_FD_FETCH_RESUME #x00000040) +(defconstant $SQL_FD_FETCH_BOOKMARK #x00000080) + +#| +;; SQL_TXN_ISOLATION_OPTION masks +) +(defconstant $SQL_TXN_READ_UNCOMMITTED #x00000001L) +(defconstant $SQL_TXN_READ_COMMITTED #x00000002L) +(defconstant $SQL_TXN_REPEATABLE_READ #x00000004L) +(defconstant $SQL_TXN_SERIALIZABLE #x00000008L) +(defconstant $SQL_TXN_VERSIONING #x00000010L + +;; SQL_CORRELATION_NAME values +) +(defconstant $SQL_CN_NONE #x0000) +(defconstant $SQL_CN_DIFFERENT #x0001) +(defconstant $SQL_CN_ANY #x0002 + +;; SQL_NON_NULLABLE_COLUMNS values +) +(defconstant $SQL_NNC_NULL #x0000) +(defconstant $SQL_NNC_NON_NULL #x0001 + +;; SQL_NULL_COLLATION values + ) +(defconstant $SQL_NC_HIGH #x0000) +(defconstant $SQL_NC_LOW #x0001) +(defconstant $SQL_NC_START #x0002) +(defconstant $SQL_NC_END #x0004 + +;; SQL_FILE_USAGE values +) +(defconstant $SQL_FILE_NOT_SUPPORTED #x0000) +(defconstant $SQL_FILE_TABLE #x0001) +(defconstant $SQL_FILE_QUALIFIER #x0002 + +;; SQL_GETDATA_EXTENSIONS values +) +(defconstant $SQL_GD_ANY_COLUMN #x00000001L) +(defconstant $SQL_GD_ANY_ORDER #x00000002L) +(defconstant $SQL_GD_BLOCK #x00000004L) +(defconstant $SQL_GD_BOUND #x00000008L + +;; SQL_ALTER_TABLE values +) +(defconstant $SQL_AT_ADD_COLUMN #x00000001L) +(defconstant $SQL_AT_DROP_COLUMN #x00000002L + +;; SQL_POSITIONED_STATEMENTS masks +) +(defconstant $SQL_PS_POSITIONED_DELETE #x00000001L) +(defconstant $SQL_PS_POSITIONED_UPDATE #x00000002L) +(defconstant $SQL_PS_SELECT_FOR_UPDATE #x00000004L + +;; SQL_GROUP_BY values +) +(defconstant $SQL_GB_NOT_SUPPORTED #x0000) +(defconstant $SQL_GB_GROUP_BY_EQUALS_SELECT #x0001) +(defconstant $SQL_GB_GROUP_BY_CONTAINS_SELECT #x0002) +(defconstant $SQL_GB_NO_RELATION #x0003 + +;; SQL_OWNER_USAGE masks +) +(defconstant $SQL_OU_DML_STATEMENTS #x00000001L) +(defconstant $SQL_OU_PROCEDURE_INVOCATION #x00000002L) +(defconstant $SQL_OU_TABLE_DEFINITION #x00000004L) +(defconstant $SQL_OU_INDEX_DEFINITION #x00000008L) +(defconstant $SQL_OU_PRIVILEGE_DEFINITION #x00000010L + +;; SQL_QUALIFIER_USAGE masks +) +(defconstant $SQL_QU_DML_STATEMENTS #x00000001L) +(defconstant $SQL_QU_PROCEDURE_INVOCATION #x00000002L) +(defconstant $SQL_QU_TABLE_DEFINITION #x00000004L) +(defconstant $SQL_QU_INDEX_DEFINITION #x00000008L) +(defconstant $SQL_QU_PRIVILEGE_DEFINITION #x00000010L + +;; SQL_SUBQUERIES masks +) +(defconstant $SQL_SQ_COMPARISON #x00000001L) +(defconstant $SQL_SQ_EXISTS #x00000002L) +(defconstant $SQL_SQ_IN #x00000004L) +(defconstant $SQL_SQ_QUANTIFIED #x00000008L) +(defconstant $SQL_SQ_CORRELATED_SUBQUERIES #x00000010L + +;; SQL_UNION masks +) +(defconstant $SQL_U_UNION #x00000001L) +(defconstant $SQL_U_UNION_ALL #x00000002L + +;; SQL_BOOKMARK_PERSISTENCE values +) +(defconstant $SQL_BP_CLOSE #x00000001L) +(defconstant $SQL_BP_DELETE #x00000002L) +(defconstant $SQL_BP_DROP #x00000004L) +(defconstant $SQL_BP_TRANSACTION #x00000008L) +(defconstant $SQL_BP_UPDATE #x00000010L) +(defconstant $SQL_BP_OTHER_HSTMT #x00000020L) +(defconstant $SQL_BP_SCROLL #x00000040L + +;; SQL_STATIC_SENSITIVITY values +) +(defconstant $SQL_SS_ADDITIONS #x00000001L) +(defconstant $SQL_SS_DELETIONS #x00000002L) +(defconstant $SQL_SS_UPDATES #x00000004L + +;; SQL_LOCK_TYPESL masks +) +(defconstant $SQL_LCK_NO_CHANGE #x00000001L) +(defconstant $SQL_LCK_EXCLUSIVE #x00000002L) +(defconstant $SQL_LCK_UNLOCK #x00000004L + +;; SQL_POS_OPERATIONS masks +|# + +(defconstant $SQL_POS_POSITION 1) ;; #x00000001L +(defconstant $SQL_POS_REFRESH 2) ;; #x00000002L +(defconstant $SQL_POS_UPDATE 4) ;; #x00000004L +(defconstant $SQL_POS_DELETE 8) ;; #x00000008L +(defconstant $SQL_POS_ADD 16) ;; #x00000010L + +#| +;; SQL_QUALIFIER_LOCATION values +) +(defconstant $SQL_QL_START #x0001L) +(defconstant $SQL_QL_END #x0002L + +;; SQL_OJ_CAPABILITIES values + +(defconstant $SQL_OJ_LEFT #x00000001L) +(defconstant $SQL_OJ_RIGHT #x00000002L) +(defconstant $SQL_OJ_FULL #x00000004L) +(defconstant $SQL_OJ_NESTED #x00000008L) +(defconstant $SQL_OJ_NOT_ORDERED #x00000010L) +(defconstant $SQL_OJ_INNER #x00000020L) +(defconstant $SQL_OJ_ALL_COMPARISON_OPS #x00000040L + +;; options for SQLGetStmtOption/SQLSetStmtOption) +(defconstant $SQL_QUERY_TIMEOUT 0) +(defconstant $SQL_MAX_ROWS 1) +(defconstant $SQL_NOSCAN 2) +(defconstant $SQL_MAX_LENGTH 3) +(defconstant $SQL_ASYNC_ENABLE 4) +(defconstant $SQL_BIND_TYPE 5 +(defconstant $SQL_CURSOR_TYPE 6) +(defconstant $SQL_CONCURRENCY 7) +(defconstant $SQL_KEYSET_SIZE 8) +(defconstant $SQL_ROWSET_SIZE 9) +(defconstant $SQL_SIMULATE_CURSOR 10) +(defconstant $SQL_RETRIEVE_DATA 11) +(defconstant $SQL_USE_BOOKMARKS 12) +(defconstant $SQL_GET_BOOKMARK 13 /* GetStmtOption Only) +(defconstant $SQL_ROW_NUMBER 14 /* GetStmtOption Only) +(defconstant $SQL_STMT_OPT_MAX SQL_ROW_NUMBER +) +(defconstant $SQL_STMT_OPT_MIN SQL_QUERY_TIMEOUT + + +;; SQL_QUERY_TIMEOUT options) +(defconstant $SQL_QUERY_TIMEOUT_DEFAULT 0UL + +;; SQL_MAX_ROWS options) +(defconstant $SQL_MAX_ROWS_DEFAULT 0UL + +;; SQL_NOSCAN options) +(defconstant $SQL_NOSCAN_OFF 0UL /* 1.0 FALSE) +(defconstant $SQL_NOSCAN_ON 1UL /* 1.0 TRUE) +(defconstant $SQL_NOSCAN_DEFAULT SQL_NOSCAN_OFF + +;; SQL_MAX_LENGTH options) +(defconstant $SQL_MAX_LENGTH_DEFAULT 0UL + +;; SQL_ASYNC_ENABLE options) +(defconstant $SQL_ASYNC_ENABLE_OFF 0UL) +(defconstant $SQL_ASYNC_ENABLE_ON 1UL) +(defconstant $SQL_ASYNC_ENABLE_DEFAULT SQL_ASYNC_ENABLE_OFF + +;; SQL_BIND_TYPE options) +(defconstant $SQL_BIND_BY_COLUMN 0UL) +(defconstant $SQL_BIND_TYPE_DEFAULT SQL_BIND_BY_COLUMN ;; Default value + +;; SQL_CONCURRENCY options) +(defconstant $SQL_CONCUR_READ_ONLY 1) +(defconstant $SQL_CONCUR_LOCK 2) +(defconstant $SQL_CONCUR_ROWVER 3) +(defconstant $SQL_CONCUR_VALUES 4) +(defconstant $SQL_CONCUR_DEFAULT SQL_CONCUR_READ_ONLY ;; Default value + +;; SQL_CURSOR_TYPE options) +(defconstant $SQL_CURSOR_FORWARD_ONLY 0UL) +(defconstant $SQL_CURSOR_KEYSET_DRIVEN 1UL) +(defconstant $SQL_CURSOR_DYNAMIC 2UL) +(defconstant $SQL_CURSOR_STATIC 3UL) +(defconstant $SQL_CURSOR_TYPE_DEFAULT SQL_CURSOR_FORWARD_ONLY ;; Default value + +;; SQL_ROWSET_SIZE options) +(defconstant $SQL_ROWSET_SIZE_DEFAULT 1UL + +;; SQL_KEYSET_SIZE options) +(defconstant $SQL_KEYSET_SIZE_DEFAULT 0UL + +;; SQL_SIMULATE_CURSOR options) +(defconstant $SQL_SC_NON_UNIQUE 0UL) +(defconstant $SQL_SC_TRY_UNIQUE 1UL) +(defconstant $SQL_SC_UNIQUE 2UL + +;; SQL_RETRIEVE_DATA options) +(defconstant $SQL_RD_OFF 0UL) +(defconstant $SQL_RD_ON 1UL) +(defconstant $SQL_RD_DEFAULT SQL_RD_ON + +;; SQL_USE_BOOKMARKS options) +(defconstant $SQL_UB_OFF 0UL) +(defconstant $SQL_UB_ON 1UL) +(defconstant $SQL_UB_DEFAULT SQL_UB_OFF + + +|# + +;; options for SQLSetConnectOption/SQLGetConnectOption) +(defconstant $SQL_ACCESS_MODE 101) +(defconstant $SQL_AUTOCOMMIT 102) +(defconstant $SQL_LOGIN_TIMEOUT 103) +(defconstant $SQL_OPT_TRACE 104) +(defconstant $SQL_OPT_TRACEFILE 105) +(defconstant $SQL_TRANSLATE_DLL 106) +(defconstant $SQL_TRANSLATE_OPTION 107) +(defconstant $SQL_TXN_ISOLATION 108) +(defconstant $SQL_CURRENT_QUALIFIER 109) +(defconstant $SQL_ODBC_CURSORS 110) +(defconstant $SQL_QUIET_MODE 111) +(defconstant $SQL_PACKET_SIZE 112) +(defconstant $SQL_CONN_OPT_MAX $SQL_PACKET_SIZE) +(defconstant $SQL_CONNECT_OPT_DRVR_START 1000) + +;;#define SQL_CONN_OPT_MIN SQL_ACCESS_MODE + +;; SQL_ACCESS_MODE options +(defconstant $SQL_MODE_READ_WRITE 0) ; 0UL +(defconstant $SQL_MODE_READ_ONLY 1) ; 1UL +(defconstant $SQL_MODE_DEFAULT $SQL_MODE_READ_WRITE) + +;; SQL_AUTOCOMMIT options) +(defconstant $SQL_AUTOCOMMIT_OFF 0) ;0UL +(defconstant $SQL_AUTOCOMMIT_ON 1) ;1UL +(defconstant $SQL_AUTOCOMMIT_DEFAULT $SQL_AUTOCOMMIT_ON) + +;; SQL_LOGIN_TIMEOUT options) +(defconstant $SQL_LOGIN_TIMEOUT_DEFAULT 15) ; 15UL + +;; SQL_OPT_TRACE options) +(defconstant $SQL_OPT_TRACE_OFF 0) ; 0UL +(defconstant $SQL_OPT_TRACE_ON 1) ; 1UL +(defconstant $SQL_OPT_TRACE_DEFAULT $SQL_OPT_TRACE_OFF) +; #ifndef SQL_OPT_TRACE_FILE_DEFAULT +; (defconstant $SQL_OPT_TRACE_FILE_DEFAULT "\\SQL.LOG" +;; #endif + +(defconstant $SQL_CUR_USE_IF_NEEDED 0) ; 0UL +(defconstant $SQL_CUR_USE_ODBC 1) ; 1UL +(defconstant $SQL_CUR_USE_DRIVER 2) ; 2UL +(defconstant $SQL_CUR_DEFAULT $SQL_CUR_USE_DRIVER) + +#| +;; Column types and scopes in SQLSpecialColumns. ) +(defconstant $SQL_BEST_ROWID 1) +(defconstant $SQL_ROWVER 2) +) +(defconstant $SQL_SCOPE_CURROW 0) +(defconstant $SQL_SCOPE_TRANSACTION 1) +(defconstant $SQL_SCOPE_SESSION 2 + +;; Defines for SQLSetPos) +(defconstant $SQL_ENTIRE_ROWSET 0 +|# + +;; Operations in SQLSetPos + +(defconstant $SQL_POSITION 0) ;; 1.0 FALSE +(defconstant $SQL_REFRESH 1) ;; 1.0 TRUE +(defconstant $SQL_UPDATE 2) +(defconstant $SQL_DELETE 3) +(defconstant $SQL_ADD 4) + +;; Lock options in SQLSetPos) +(defconstant $SQL_LOCK_NO_CHANGE 0) ;; 1.0 FALSE +(defconstant $SQL_LOCK_EXCLUSIVE 1) ;; 1.0 TRUE +(defconstant $SQL_LOCK_UNLOCK 2) + +;; SQLBindParameter extensions +(defconstant $SQL_DEFAULT_PARAM -5) +(defconstant $SQL_IGNORE -6) +(defconstant $SQL_LEN_DATA_AT_EXEC_OFFSET -100) +;(defconstant $SQL_LEN_DATA_AT_EXEC(length) (-length+SQL_LEN_DATA_AT_EXEC_OFFSET) + +;; Special return values for SQLGetData +(defconstant $SQL_NO_TOTAL -4) + +#| +;; Macros for SQLSetPos) +(defconstant $SQL_POSITION_TO(hstmt,irow) SQLSetPos(hstmt,irow,SQL_POSITION,SQL_LOCK_NO_CHANGE)) +(defconstant $SQL_LOCK_RECORD(hstmt,irow,fLock) SQLSetPos(hstmt,irow,SQL_POSITION,fLock)) +(defconstant $SQL_REFRESH_RECORD(hstmt,irow,fLock) SQLSetPos(hstmt,irow,SQL_REFRESH,fLock)) +(defconstant $SQL_UPDATE_RECORD(hstmt,irow) SQLSetPos(hstmt,irow,SQL_UPDATE,SQL_LOCK_NO_CHANGE)) +(defconstant $SQL_DELETE_RECORD(hstmt,irow) SQLSetPos(hstmt,irow,SQL_DELETE,SQL_LOCK_NO_CHANGE)) +(defconstant $SQL_ADD_RECORD(hstmt,irow) SQLSetPos(hstmt,irow,SQL_ADD,SQL_LOCK_NO_CHANGE) + +; #ifndef RC_INVOKED + +/* This define is too large for RC) +(defconstant $SQL_ODBC_KEYWORDS \ +"ABSOLUTE,ACTION,ADA,ADD,ALL,ALLOCATE,ALTER,AND,ANY,ARE,AS,"\ +"ASC,ASSERTION,AT,AUTHORIZATION,AVG,"\ +"BEGIN,BETWEEN,BIT,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,CATALOG,"\ +"CHAR,CHAR_LENGTH,CHARACTER,CHARACTER_LENGTH,CHECK,CLOSE,COALESCE,"\ +"COBOL,COLLATE,COLLATION,COLUMN,COMMIT,CONNECT,CONNECTION,CONSTRAINT,"\ +"CONSTRAINTS,CONTINUE,CONVERT,CORRESPONDING,COUNT,CREATE,CROSS,CURRENT,"\ +"CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,CURSOR,"\ +"DATE,DAY,DEALLOCATE,DEC,DECIMAL,DECLARE,DEFAULT,DEFERRABLE,"\ +"DEFERRED,DELETE,DESC,DESCRIBE,DESCRIPTOR,DIAGNOSTICS,DISCONNECT,"\ +"DISTINCT,DOMAIN,DOUBLE,DROP,"\ +"ELSE,END,END-EXEC,ESCAPE,EXCEPT,EXCEPTION,EXEC,EXECUTE,"\ +"EXISTS,EXTERNAL,EXTRACT,"\ +"FALSE,FETCH,FIRST,FLOAT,FOR,FOREIGN,FORTRAN,FOUND,FROM,FULL,"\ +"GET,GLOBAL,GO,GOTO,GRANT,GROUP,HAVING,HOUR,"\ +"IDENTITY,IMMEDIATE,IN,INCLUDE,INDEX,INDICATOR,INITIALLY,INNER,"\ +"INPUT,INSENSITIVE,INSERT,INTEGER,INTERSECT,INTERVAL,INTO,IS,ISOLATION,"\ +"JOIN,KEY,LANGUAGE,LAST,LEADING,LEFT,LEVEL,LIKE,LOCAL,LOWER,"\ +"MATCH,MAX,MIN,MINUTE,MODULE,MONTH,MUMPS,"\ +"NAMES,NATIONAL,NATURAL,NCHAR,NEXT,NO,NONE,NOT,NULL,NULLIF,NUMERIC,"\ +"OCTET_LENGTH,OF,ON,ONLY,OPEN,OPTION,OR,ORDER,OUTER,OUTPUT,OVERLAPS,"\ +"PAD,PARTIAL,PASCAL,PLI,POSITION,PRECISION,PREPARE,PRESERVE,"\ +"PRIMARY,PRIOR,PRIVILEGES,PROCEDURE,PUBLIC,"\ +"REFERENCES,RELATIVE,RESTRICT,REVOKE,RIGHT,ROLLBACK,ROWS,"\ +"SCHEMA,SCROLL,SECOND,SECTION,SELECT,SEQUENCE,SESSION,SESSION_USER,SET,SIZE,"\ +"SMALLINT,SOME,SPACE,SQL,SQLCA,SQLCODE,SQLERROR,SQLSTATE,SQLWARNING,"\ +"SUBSTRING,SUM,SYSTEM_USER,"\ +"TABLE,TEMPORARY,THEN,TIME,TIMESTAMP,TIMEZONE_HOUR,TIMEZONE_MINUTE,"\ +"TO,TRAILING,TRANSACTION,TRANSLATE,TRANSLATION,TRIM,TRUE,"\ +"UNION,UNIQUE,UNKNOWN,UPDATE,UPPER,USAGE,USER,USING,"\ +"VALUE,VALUES,VARCHAR,VARYING,VIEW,WHEN,WHENEVER,WHERE,WITH,WORK,YEAR") +|# + +(defconstant $SQL_PARAM_TYPE_UNKNOWN 0) +(defconstant $SQL_PARAM_INPUT 1) +(defconstant $SQL_PARAM_INPUT_OUTPUT 2) +(defconstant $SQL_RESULT_COL 3) +(defconstant $SQL_PARAM_OUTPUT 4) +(defconstant $SQL_RETURN_VALUE 5) + + +;; Defines used by both Level 1 and Level 2 functions + +;; generally useful constants +(defconstant $SQL_MAX_OPTION_STRING_LENGTH 256) + +;; Additional return codes) +(defconstant $SQL_STILL_EXECUTING 2) +(defconstant $SQL_NEED_DATA 99) + +;; SQL extended datatypes) +(defconstant $SQL_DATE 9) +(defconstant $SQL_TIME 10) +(defconstant $SQL_TIMESTAMP 11) +(defconstant $SQL_LONGVARCHAR -1) +(defconstant $SQL_BINARY -2) +(defconstant $SQL_VARBINARY -3) +(defconstant $SQL_LONGVARBINARY -4) +(defconstant $SQL_BIGINT -5) +(defconstant $SQL_TINYINT -6) +(defconstant $SQL_BIT -7) + +;; For ODBC3 +(defconstant $SQL_TYPE_DATE 91) +(defconstant $SQL_TYPE_TIME 92) +(defconstant $SQL_TYPE_TIMESTAMP 93) + +(defconstant $SQL_INTERVAL_YEAR -80) +(defconstant $SQL_INTERVAL_MONTH -81) +(defconstant $SQL_INTERVAL_YEAR_TO_MONTH -82) +(defconstant $SQL_INTERVAL_DAY -83) +(defconstant $SQL_INTERVAL_HOUR -84) +(defconstant $SQL_INTERVAL_MINUTE -85) +(defconstant $SQL_INTERVAL_SECOND -86) +(defconstant $SQL_INTERVAL_DAY_TO_HOUR -87) +(defconstant $SQL_INTERVAL_DAY_TO_MINUTE -88) +(defconstant $SQL_INTERVAL_DAY_TO_SECOND -89) +(defconstant $SQL_INTERVAL_HOUR_TO_MINUTE -90) +(defconstant $SQL_INTERVAL_HOUR_TO_SECOND -91) +(defconstant $SQL_INTERVAL_MINUTE_TO_SECOND -92) +(defconstant $SQL_UNICODE -95) +(defconstant $SQL_TYPE_DRIVER_START $SQL_INTERVAL_YEAR) +(defconstant $SQL_TYPE_DRIVER_END $SQL_UNICODE) + + +(defconstant $SQL_SIGNED_OFFSET -20) +(defconstant $SQL_UNSIGNED_OFFSET -22) + +;; C datatype to SQL datatype mapping +(defconstant $SQL_C_DATE $SQL_DATE) +(defconstant $SQL_C_TIME $SQL_TIME) +(defconstant $SQL_C_TIMESTAMP $SQL_TIMESTAMP) +(defconstant $SQL_C_BINARY $SQL_BINARY) +(defconstant $SQL_C_BIT $SQL_BIT) +(defconstant $SQL_C_TINYINT $SQL_TINYINT) +(defconstant $SQL_C_SBIGINT (+ $SQL_BIGINT $SQL_SIGNED_OFFSET)) +(defconstant $SQL_C_SLONG (+ $SQL_C_LONG $SQL_SIGNED_OFFSET)) ;; SIGNED INTEGER +(defconstant $SQL_C_SSHORT (+ $SQL_C_SHORT $SQL_SIGNED_OFFSET)) ;; SIGNED SMALLINT +(defconstant $SQL_C_STINYINT (+ $SQL_TINYINT $SQL_SIGNED_OFFSET)) ;; SIGNED TINYINT +(defconstant $SQL_C_ULONG (+ $SQL_C_LONG $SQL_UNSIGNED_OFFSET)) ;; UNSIGNED INTEGER +(defconstant $SQL_C_USHORT (+ $SQL_C_SHORT $SQL_UNSIGNED_OFFSET)) ;; UNSIGNED SMALLINT +(defconstant $SQL_C_UTINYINT (+ $SQL_TINYINT $SQL_UNSIGNED_OFFSET)) ;;UNSIGNED TINYINT +(defconstant $SQL_C_BOOKMARK $SQL_C_ULONG) ;; BOOKMARK + +;;; ODBC3 +(defconstant $SQL_C_TYPE_DATE $SQL_TYPE_DATE) +(defconstant $SQL_C_TYPE_TIME $SQL_TYPE_TIME) +(defconstant $SQL_C_TYPE_TIMESTAMP $SQL_TYPE_TIMESTAMP) + +;; Options for SQLDriverConnect +(defconstant $SQL_DRIVER_NOPROMPT 0) +(defconstant $SQL_DRIVER_COMPLETE 1) +(defconstant $SQL_DRIVER_PROMPT 2) +(defconstant $SQL_DRIVER_COMPLETE_REQUIRED 3) + +(defconstant $SQL_MAX_CONN_OUT 1024) + +;; Level 2 Functions + +;; SQLExtendedFetch "fFetchType" values +(defconstant $SQL_FETCH_NEXT 1) +(defconstant $SQL_FETCH_FIRST 2) +(defconstant $SQL_FETCH_LAST 3) +(defconstant $SQL_FETCH_PRIOR 4) +(defconstant $SQL_FETCH_ABSOLUTE 5) +(defconstant $SQL_FETCH_RELATIVE 6) +(defconstant $SQL_FETCH_BOOKMARK 8) + +;;; ODBC3 constants, added by KMR + +(defconstant $SQL_ATTR_ODBC_VERSION 200) +(defconstant $SQL_OV_ODBC2 2) +(defconstant $SQL_OV_ODBC3 3) +(defconstant $SQL_INDEX_UNIQUE 0) +(defconstant $SQL_INDEX_ALL 1) +(defconstant $SQL_QUICK 0) +(defconstant $SQL_ENSURE 1) + + diff --git a/db-odbc/odbc-dbi.lisp b/db-odbc/odbc-dbi.lisp new file mode 100644 index 0000000..90cea24 --- /dev/null +++ b/db-odbc/odbc-dbi.lisp @@ -0,0 +1,706 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-dbi.cl +;;;; Purpose: Mid-level (DBI) interface for CLSQL ODBC backend +;;;; Author: Kevin M. Rosenberg +;;;; Create: April 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:odbc-dbi + (:use #:cl #:odbc) + (:export + #:bind-parameter + #:close-query + #:connect + #:db-external-format + #:db-hstmt + #:db-width + #:disconnect + #:end-transaction + #:fetch-row + #:list-all-data-sources + #:list-all-database-tables + #:list-all-table-columns + #:list-table-indexes + #:loop-over-results + #:prepare-sql + #:rr-sql + #:run-prepared-sql + #:set-autocommit + #:sql + + #:*auto-trim-strings* + #:*default-database* + #:*default-odbc-external-format* + #:*null-value* + ) + (:documentation "This is the mid-level interface ODBC.")) + +(in-package #:odbc-dbi) + +(defgeneric terminate (src)) +(defgeneric db-open-query (src query-expression + &key arglen col-positions result-types width + &allow-other-keys)) +(defgeneric db-fetch-query-results (src &optional count)) +(defgeneric %db-execute (src sql-expression &key &allow-other-keys)) +(defgeneric db-execute-command (src sql-string)) + +(defgeneric %initialize-query (src arglen col-positions + &key result-types width)) + +(defgeneric %read-query-data (src ignore-columns)) +(defgeneric db-map-query (src type function query-exp &key result-types)) +(defgeneric db-prepare-statement (src sql &key parameter-table + parameter-columns)) +(defgeneric get-odbc-info (src info-type)) + +(defvar *reuse-query-objects* t) + + +;;; SQL Interface + +(defclass odbc-db () + (;; any reason to have more than one henv? + (width :initform +max-precision+ :accessor db-width) + (hstmt :initform nil :accessor db-hstmt) + (henv :initform nil :allocation :class :initarg :henv :accessor henv) + (hdbc :initform nil :initarg :hdbc :accessor hdbc) + ;; info returned from SQLGetInfo + (info :initform (make-hash-table) :reader db-info) + (type :initform nil :initarg :db-type :reader db-type) + (connected-p :initform nil :accessor db-connected-p) + ;; not used yet + (count :initform 0 :initarg :count :accessor db-count) + ;; not used yet + (total-count :initform 0 :allocation :class :accessor db-total-count) + ;; the use of this slot is deprecated; it will be removed when dtf works without it. + (query :initform nil :accessor db-query-object) + ;; resource of (active and inactive) query objects + (queries :initform () :accessor db-queries))) + +(defclass odbc-query () + ((hstmt :initform nil :initarg :hstmt :accessor hstmt) ; = cursor?? + (width :initform +max-precision+ :accessor query-width) + (computed-result-types :initform nil :initarg :computed-result-types :accessor computed-result-types) + (column-count :initform nil :accessor column-count) + (column-names :initform (make-array 0 :element-type 'string :adjustable t :fill-pointer t) + :accessor column-names) + (column-c-types :initform (make-array 0 :element-type 'fixnum :adjustable t :fill-pointer t) + :accessor column-c-types) + (column-sql-types :initform (make-array 0 :element-type 'fixnum :adjustable t :fill-pointer t) + :accessor column-sql-types) + (column-data-ptrs :initform (make-array 0 :adjustable t :fill-pointer t) + :accessor data-ptrs) + (column-out-len-ptrs :initform (make-array 0 :adjustable t :fill-pointer t) + :accessor column-out-len-ptrs) + (column-precisions :initform (make-array 0 :element-type 'integer :adjustable t :fill-pointer t) + :accessor column-precisions) + (column-scales :initform (make-array 0 :element-type 'fixnum :adjustable t :fill-pointer t) + :accessor column-scales) + (column-nullables-p :initform (make-array 0 :element-type 'fixnum :adjustable t :fill-pointer t) + :accessor column-nullables-p) + ;;(parameter-count :initform 0 :accessor parameter-count) + (parameter-data-ptrs :initform (make-array 0 :adjustable t :fill-pointer t) + :accessor parameter-ptrs) + ;; a query string or a query expression object + (sql-expression :initform nil :initarg :sql-expression :accessor sql-expression) + ;; database object the query is to be run against + (database :initarg :database :reader query-database) + (active-p :initform nil :initarg :active-p :accessor query-active-p)) + (:documentation + "Stores query information, like SQL query string/expression and database to run +the query against." )) + +;;; AODBC Compatible interface + +(defun connect (&key data-source-name user password connection-string completion window-handle (autocommit t)) + (let ((db (make-instance 'odbc-db))) + (unless (henv db) ;; has class allocation! + (setf (henv db) (%new-environment-handle))) + (setf (hdbc db) (%new-db-connection-handle (henv db))) + (if connection-string + (%sql-driver-connect (hdbc db) + connection-string + (ecase completion + (:no-prompt odbc::$SQL_DRIVER_NOPROMPT) + (:complete odbc::$SQL_DRIVER_COMPLETE) + (:prompt odbc::$SQL_DRIVER_PROMPT) + (:complete-required odbc::$SQL_DRIVER_COMPLETE_REQUIRED)) + window-handle) + (%sql-connect (hdbc db) data-source-name user password)) + #+ignore (setf (db-hstmt db) (%new-statement-handle (hdbc db))) + (when (/= (get-odbc-info db odbc::$SQL_TXN_CAPABLE) odbc::$SQL_TC_NONE) + (if autocommit + (enable-autocommit (hdbc db)) + (disable-autocommit (hdbc db)))) + db)) + +(defun disconnect (database) + "This is set in the generic-odbc-database disconnect-fn slot so xref fails + but this does get called on generic ODBC connections " + (with-slots (hdbc queries) database + (dolist (query queries) + (db-close-query query :drop-p T)) + (when (db-hstmt database) + (%free-statement (db-hstmt database) :drop)) + (%disconnect hdbc))) + + +(defun sql (expr &key db result-types row-count (column-names t) query + hstmt width) + (declare (ignore hstmt)) + (cond + (query + (let ((q (db-open-query db expr :result-types result-types :width width))) + (if column-names + (values q (column-names q)) + q))) + (t + (multiple-value-bind (data col-names) + (db-query db expr :result-types result-types :width width) + (cond + (row-count + (if (consp data) (length data) data)) + (column-names + (values data col-names)) + (t + data)))))) + +(defun fetch-row (query &optional (eof-errorp t) eof-value) + (multiple-value-bind (row query count) (db-fetch-query-results query 1) + (cond + ((zerop count) + (close-query query) + (when eof-errorp + (error 'clsql:sql-database-data-error + :message "ODBC: Ran out of data in fetch-row")) + eof-value) + (t + (car row))))) + + +(defun close-query (query) + (db-close-query query)) + +(defun list-all-database-tables (&key db hstmt) + (declare (ignore hstmt)) + (let ((query (get-free-query db))) + (unwind-protect + (progn + (with-slots (hstmt) query + (unless hstmt (setf hstmt (%new-statement-handle (hdbc db)))) + (%list-tables hstmt) + (%initialize-query query nil nil) + (values + (db-fetch-query-results query) + (coerce (column-names query) 'list)))) + (db-close-query query)))) + +(defun list-table-indexes (table &key db unique hstmt + &aux (table + (princ-to-string + (clsql-sys::unescaped-database-identifier table)))) + (declare (ignore hstmt)) + (let ((query (get-free-query db))) + (unwind-protect + (progn + (with-slots (hstmt) query + (unless hstmt + (setf hstmt (%new-statement-handle (hdbc db)))) + (%table-statistics table hstmt :unique unique) + (%initialize-query query nil nil) + (values + (db-fetch-query-results query) + (coerce (column-names query) 'list)))) + (db-close-query query)))) + +(defun list-all-table-columns (table &key db hstmt + &aux (table + (princ-to-string + (clsql-sys::unescaped-database-identifier table)))) + (declare (ignore hstmt)) + (db-describe-columns db nil nil table nil)) ;; use nil rather than "" for unspecified values + +(defun list-all-data-sources () + (let ((db (make-instance 'odbc-db))) + (unless (henv db) ;; has class allocation! + (setf (henv db) (%new-environment-handle))) + (%list-data-sources (henv db)))) + +(defun rr-sql (hstmt sql-statement &key db) + (declare (ignore hstmt sql-statement db)) + (warn "rr-sql not implemented.")) + +;;; Mid-level interface + +(defun db-commit (database) + (%commit (henv database) (hdbc database))) + +(defun db-rollback (database) + (%rollback (henv database) (hdbc database))) + +(defun db-cancel-query (query) + (with-slots (hstmt) query + (%sql-cancel hstmt) + (setf (query-active-p query) nil))) + +#+simple-version +(defmacro with-transaction (&body body) + `(%with-transaction + (:henv (henv ,*default-database*) :hdbc (hdbc ,*default-database*)) + ,@body)) + +(defmethod initialize-instance :after ((query odbc-query) + &key sql henv hdbc &allow-other-keys) + (when sql + (let ((hstmt (%new-statement-handle hdbc))) + (%sql-exec-direct sql hstmt henv hdbc) + (with-slots (column-count + column-names column-c-types column-sql-types column-data-ptrs + column-out-len-ptrs column-precisions column-scales + column-nullables-p active-p) query + (setf (hstmt query) hstmt) + (%initialize-query query nil nil) + (setf active-p t))))) + +;; one for odbc-db is missing +;; TODO: Seems to be uncalled +(defmethod terminate ((query odbc-query)) + ;;(format tb::*local-output* "~%*** terminated: ~s" query) + (db-close-query query)) + +(defun %dispose-column-ptrs (query) + "frees memory allocated for query object column-data and column-data-length" + (with-slots (column-data-ptrs column-out-len-ptrs hstmt) query + (loop for data-ptr across column-data-ptrs + for out-len-ptr across column-out-len-ptrs + when data-ptr + do (uffi:free-foreign-object data-ptr) + when out-len-ptr + do (uffi:free-foreign-object out-len-ptr)) + (setf (fill-pointer column-data-ptrs) 0 + (fill-pointer column-out-len-ptrs) 0))) + +(defmethod db-open-query ((database odbc-db) query-expression + &key arglen col-positions result-types width + &allow-other-keys) + (db-open-query (get-free-query database) query-expression + :arglen arglen :col-positions col-positions + :result-types result-types + :width (if width width (db-width database)))) + +(defmethod db-open-query ((query odbc-query) query-expression + &key arglen col-positions result-types width + &allow-other-keys) + (%db-execute query query-expression) + (%initialize-query query arglen col-positions :result-types result-types + :width width)) + +(defmethod db-fetch-query-results ((database odbc-db) &optional count) + (db-fetch-query-results (db-query-object database) count)) + +(defmethod db-fetch-query-results ((query odbc-query) &optional count) + (when (query-active-p query) + (with-slots (column-count column-data-ptrs column-c-types column-sql-types + column-out-len-ptrs column-precisions hstmt computed-result-types) + query + (let* ((rows-fetched 0) + (rows + (loop for i from 0 + until (or (and count (= i count)) + (= (%sql-fetch hstmt) odbc::$SQL_NO_DATA_FOUND)) + collect + (loop for result-type across computed-result-types + for data-ptr across column-data-ptrs + for c-type across column-c-types + for sql-type across column-sql-types + for out-len-ptr across column-out-len-ptrs + for precision across column-precisions + for j from 0 ; column count is zero based in lisp + collect + (progn + (incf rows-fetched) + (cond ((< 0 precision (query-width query)) + (read-data data-ptr c-type sql-type out-len-ptr result-type)) + ((zerop (get-cast-long out-len-ptr)) + nil) + (t + (read-data-in-chunks hstmt j data-ptr c-type sql-type + out-len-ptr result-type)))))))) + (values rows query rows-fetched))))) + +(defun db-query (database query-expression &key result-types width) + (let ((free-query (get-free-query database))) + (setf (sql-expression free-query) query-expression) + (unwind-protect + (progn + (%db-execute free-query query-expression) + (%initialize-query free-query nil nil :result-types result-types :width width) + (if (plusp (column-count free-query)) ;; KMR: Added check for commands that don't return columns + (values + (db-fetch-query-results free-query nil) + (map 'list #'identity (column-names free-query))) + (values + (result-rows-count (hstmt free-query)) + nil))) + (db-close-query free-query) + ))) + +(defmethod %db-execute ((database odbc-db) sql-expression &key &allow-other-keys) + (%db-execute (get-free-query database) sql-expression)) + +(defmethod %db-execute ((query odbc-query) sql-expression &key &allow-other-keys) + (with-slots (henv hdbc) (odbc::query-database query) + (with-slots (hstmt) query + (unless hstmt (setf hstmt (%new-statement-handle hdbc))) + (setf (sql-expression query) sql-expression) + (%sql-exec-direct sql-expression hstmt henv hdbc) + query))) + +;; reuse inactive queries +(defun get-free-query (database) + "get-free-query finds or makes a nonactive query object, and then sets it to active. +This makes the functions db-execute-command and db-query thread safe." + (with-slots (queries hdbc) database + (or (and *reuse-query-objects* + (clsql-sys:without-interrupts + (let ((inactive-query (find-if (lambda (query) + (not (query-active-p query))) + queries))) + (when inactive-query + (with-slots (column-count column-names column-c-types + width hstmt + column-sql-types column-data-ptrs + column-out-len-ptrs column-precisions + column-scales column-nullables-p) + inactive-query + (setf column-count 0 + width +max-precision+ + ;; KMR hstmt (%new-statement-handle hdbc) + (fill-pointer column-names) 0 + (fill-pointer column-c-types) 0 + (fill-pointer column-sql-types) 0 + (fill-pointer column-data-ptrs) 0 + (fill-pointer column-out-len-ptrs) 0 + (fill-pointer column-precisions) 0 + (fill-pointer column-scales) 0 + (fill-pointer column-nullables-p) 0)) + (setf (query-active-p inactive-query) t)) + inactive-query))) + (let ((new-query (make-instance 'odbc-query + :database database + ;;(clone-database database) + :active-p t))) + (push new-query queries) + new-query)))) + +(defmethod db-execute-command ((database odbc-db) sql-string) + (db-execute-command (get-free-query database) sql-string)) + +(defmethod db-execute-command ((query odbc-query) sql-string) + (with-slots (hstmt database) query + (with-slots (henv hdbc) database + (unless hstmt (setf hstmt (%new-statement-handle hdbc))) + (unwind-protect + (%sql-exec-direct sql-string hstmt henv hdbc) + (db-close-query query))))) + +(defmethod %initialize-query ((database odbc-db) arglen col-positions &key result-types width) + (%initialize-query (db-query-object database) arglen col-positions + :result-types result-types + :width (if width width (db-width database)))) + +(defmethod %initialize-query ((query odbc-query) arglen col-positions &key result-types width) + (with-slots (hstmt computed-result-types + column-count column-names column-c-types column-sql-types + column-data-ptrs column-out-len-ptrs column-precisions + column-scales column-nullables-p) + query + (setf column-count (if arglen + (min arglen (result-columns-count hstmt)) + (result-columns-count hstmt))) + (when width (setf (query-width query) width)) + ;;(format tb::*local-output* "~%column-count: ~d, col-positions: ~d" column-count col-positions) + (labels ((initialize-column (col-nr) + (multiple-value-bind (name sql-type precision scale nullable-p) + (%describe-column hstmt (1+ col-nr)) + ;; allocate space to bind result rows to + (multiple-value-bind (c-type data-ptr out-len-ptr size long-p) + (%allocate-bindings sql-type precision) + (if long-p ;; if long-p we fetch in chunks with %sql-get-data but must ensure that out_len_ptr is non zero + (setf (uffi:deref-pointer out-len-ptr #.odbc::$ODBC-LONG-TYPE) #.odbc::$SQL_NO_TOTAL) + (%bind-column hstmt col-nr c-type data-ptr (1+ size) out-len-ptr)) + (vector-push-extend name column-names) + (vector-push-extend sql-type column-sql-types) + (vector-push-extend (sql-to-c-type sql-type) column-c-types) + (vector-push-extend precision column-precisions) + (vector-push-extend scale column-scales) + (vector-push-extend nullable-p column-nullables-p) + (vector-push-extend data-ptr column-data-ptrs) + (vector-push-extend out-len-ptr column-out-len-ptrs))))) + (if col-positions + (dolist (col-nr col-positions) + (initialize-column col-nr)) + (dotimes (col-nr column-count) + ;; get column information + (initialize-column col-nr)))) + + ;; TODO: move this into the above loop + (setf computed-result-types (make-array column-count)) + (dotimes (i column-count) + (setf (aref computed-result-types i) + (cond + ((consp result-types) + (nth i result-types)) + ((eq result-types :auto) + (case (aref column-c-types i) + (#.odbc::$SQL_C_SLONG :int) + (#.odbc::$SQL_C_DOUBLE :double) + (#.odbc::$SQL_C_FLOAT :float) + (#.odbc::$SQL_C_SSHORT :short) + (#.odbc::$SQL_C_STINYINT :short) + (#.odbc::$SQL_C_SBIGINT #.odbc::$ODBC-BIG-TYPE) + (#.odbc::$SQL_C_TYPE_TIMESTAMP :time) + (#.odbc::$SQL_C_CHAR ;; TODO: Read this as rational instead of double + (or (case (aref column-sql-types i) + ((#.odbc::$SQL_NUMERIC #.odbc::$SQL_DECIMAL) :double)) + T)) + + (t t))) + (t t))))) + query) + +(defun db-close-query (query &key (drop-p (not *reuse-query-objects*))) + (with-slots (hstmt column-count column-names column-c-types column-sql-types + column-data-ptrs column-out-len-ptrs column-precisions + column-scales column-nullables-p database) query + (%dispose-column-ptrs query) + (cond ((null hstmt) nil) + (drop-p + (%free-statement hstmt :drop) + ;; dont free with uffi/ this is a double free and crashes everything + ;; (uffi:free-foreign-object hstmt) + (setf hstmt nil)) + (t + (%free-statement hstmt :unbind) + (%free-statement hstmt :reset) + (%free-statement hstmt :close))) + (setf (query-active-p query) nil) + (when drop-p + (clsql-sys:without-interrupts + (with-slots (queries) database + (setf queries (remove query queries)))))) + query) + +(defmethod %read-query-data ((database odbc-db) ignore-columns) + (%read-query-data (db-query-object database) ignore-columns)) + +(defmethod %read-query-data ((query odbc-query) ignore-columns) + (with-slots (hstmt column-count column-c-types column-sql-types + column-data-ptrs column-out-len-ptrs column-precisions + computed-result-types) + query + (unless (= (odbc::SQLFetch hstmt) odbc::$SQL_NO_DATA_FOUND) + (values + (loop for col-nr from 0 to (- column-count + (if (eq ignore-columns :last) 2 1)) + for result-type across computed-result-types + collect + (let ((precision (aref column-precisions col-nr)) + (sql-type (aref column-sql-types col-nr))) + (cond ((or (< 0 precision (query-width query)) + (and (zerop precision) (not (find sql-type '($SQL_C_CHAR))))) + (read-data (aref column-data-ptrs col-nr) + (aref column-c-types col-nr) + sql-type + (aref column-out-len-ptrs col-nr) + result-type)) + ((zerop (get-cast-long (aref column-out-len-ptrs col-nr))) + *null*) + (t + (read-data-in-chunks hstmt col-nr + (aref column-data-ptrs col-nr) + (aref column-c-types col-nr) + (aref column-sql-types col-nr) + (aref column-out-len-ptrs col-nr) + result-type))))) + t)))) + +(defmethod db-map-query ((database odbc-db) type function query-exp &key result-types) + (db-map-query (get-free-query database) type function query-exp :result-types result-types)) + +(defmethod db-map-query ((query odbc-query) type function query-exp &key result-types) + (declare (ignore type)) ; preliminary. Do a type coersion here + (%db-execute query (sql-expression query-exp)) + (unwind-protect + (progn + (%initialize-query query nil nil :result-types result-types) + ;; the main loop + (loop for data = (%read-query-data query nil) + while data + do (apply function data))) + ;; dispose of memory and set query inactive or get rid of it + (db-close-query query))) + +(defun db-map-bind-query (query type function + &rest parameters) + (declare (ignore type)) ; preliminary. Do a type coersion here + (unwind-protect + (progn + (apply #'%db-bind-execute query parameters) + ;; the main loop + (loop with data + while (setf data (%read-query-data query nil)) + do (apply function data))) + ;; dispose of memory and set query inactive or get rid of it + (%db-reset-query query))) + +;; does not always return exactly a lisp type... +(defun sql-to-lisp-type (sql-type) + (ecase sql-type + ((#.odbc::$SQL_CHAR #.odbc::$SQL_VARCHAR #.odbc::$SQL_LONGVARCHAR) :string) + ((#.odbc::$SQL_NUMERIC #.odbc::$SQL_DECIMAL) :string) ; ?? + (#.odbc::$SQL_BIGINT #.odbc::$ODBC-BIG-TYPE) + (#.odbc::$SQL_INTEGER #.odbc::$ODBC-LONG-TYPE) + (#.odbc::$SQL_SMALLINT :short) + ((#.odbc::$SQL_FLOAT #.odbc::$SQL_DOUBLE) #.odbc::$ODBC-LONG-TYPE) + (#.odbc::$SQL_REAL #.odbc::$ODBC-LONG-TYPE) + ((#.odbc::$SQL_DATE #.odbc::$SQL_TYPE_DATE) 'sql-c-date) + ((#.odbc::$SQL_TIME #.odbc::$SQL_TYPE_TIME) 'sql-c-time) + ((#.odbc::$SQL_TIMESTAMP #.odbc::$SQL_TYPE_TIMESTAMP) 'sql-c-timestamp) + ;;((#.odbc::$SQL_BINARY #.odbc::$SQL_VARBINARY #.odbc::$SQL_LONGVARBINARY) odbc::$SQL_C_BINARY) ; ?? + (#.odbc::$SQL_TINYINT :short) + ;;(#.odbc::$SQL_BIT odbc::$SQL_C_BIT) ; ?? + (#.odbc::$SQL_BIT :short) + ((#.odbc::$SQL_VARBINARY #.odbc::$SQL_LONGVARBINARY) :binary) + )) + +;; prepared queries + +(defmethod db-prepare-statement ((database odbc-db) sql + &key parameter-table parameter-columns) + (with-slots (hdbc) database + (let ((query (get-free-query database))) + (with-slots (hstmt) query + (unless hstmt (setf hstmt (%new-statement-handle hdbc)))) + (db-prepare-statement + query sql :parameter-table parameter-table :parameter-columns parameter-columns)))) + +(defmethod db-prepare-statement ((query odbc-query) (sql string) + &key parameter-table parameter-columns) + ;; this is a workaround to get hold of the column types when the driver does not + ;; support SQLDescribeParam. To do: put code in here for drivers that do + ;; support it. + (unless (string-equal sql "insert" :end1 6) + (error 'clsql:sql-database-error + (format nil + "Only insert expressions are supported in literal ODBC: '~a'." sql))) + (%db-execute query (format nil "select ~{~a~^,~} from ~a where 0 = 1" + (or parameter-columns '("*")) parameter-table)) + (%initialize-query query nil nil) + (with-slots (hstmt) query + (%free-statement hstmt :unbind) + (%free-statement hstmt :reset) + (setf (sql-expression query) sql) + (%sql-prepare hstmt sql)) + query) + + +(defun %db-bind-execute (query &rest parameters) + "Only used from db-map-bind-query + parameters are released in %reset-query + " + (with-slots (hstmt parameter-data-ptrs) query + (loop for parameter in parameters + with data-ptr and size and parameter-string + do + (setf parameter-string + (if (stringp parameter) + parameter + (write-to-string parameter)) + size (length parameter-string) + data-ptr + (uffi:allocate-foreign-string (1+ size))) + (vector-push-extend data-ptr parameter-data-ptrs) + (%sql-bind-parameter + hstmt (1- (fill-pointer parameter-data-ptrs)) odbc::$SQL_PARAM_INPUT + odbc::$SQL_C_CHAR ; (aref column-c-types parameter-count) + odbc::$SQL_CHAR ; sql-type + (query-width query) ;precision ; this should be the actual precision! + ;; scale + 0 ;; should be calculated for odbc::$SQL_DECIMAL, + ;;$SQL_NUMERIC and odbc::$SQL_TIMESTAMP + data-ptr ;; = rgbValue + 0 + ;; *pcbValue; + ;; change this for output and binary input! (see 3-32) + +null-ptr+) + (%put-str data-ptr parameter-string size)) + (%sql-execute hstmt))) + + +(defun %db-reset-query (query) + "Only used from db-map-bind-query + parameters are allocated in %db-bind-execute + " + (with-slots (hstmt parameter-data-ptrs) query + (prog1 + (db-fetch-query-results query nil) + (%free-statement hstmt :reset) ;; but _not_ :unbind ! + (%free-statement hstmt :close) + (dotimes (param-nr (fill-pointer parameter-data-ptrs)) + (let ((data-ptr (aref parameter-data-ptrs param-nr))) + (when data-ptr (uffi:free-foreign-object data-ptr)))) + (setf (fill-pointer parameter-data-ptrs) 0)))) + +(defun data-parameter-ptr (hstmt) + (uffi:with-foreign-object (param-ptr :pointer-void) + (let ((return-code (%sql-param-data hstmt param-ptr))) + ;;(format t "~%return-code from %sql-param-data: ~a~%" return-code) + (when (= return-code odbc::$SQL_NEED_DATA) + ;;(ffc::%pointer-to-address (%get-ptr param-ptr)) + (uffi:deref-pointer param-ptr :pointer-void))))) + +;; database inquiery functions + +(defun db-describe-columns (database table-qualifier table-owner + table-name column-name) + (with-slots (hdbc) database + (%describe-columns hdbc table-qualifier table-owner table-name column-name))) + +;; should translate info-type integers to keywords in order to make this +;; more readable? +(defmethod get-odbc-info ((database odbc-db) info-type) + (with-slots (hdbc info) database + (or (gethash info-type info) + (setf (gethash info-type info) + (%sql-get-info hdbc info-type))))) + +(defmethod get-odbc-info ((query odbc-query) info-type) + (get-odbc-info (odbc::query-database query) info-type)) + +;; driver inquiry +;; How does this differ from list-data-sources? +(defgeneric db-data-sources (db-type)) +(defmethod db-data-sources ((db-type (eql :odbc))) + "Returns a list of (data-source description) - pairs" + (let ((henv (%new-environment-handle))) + (unwind-protect + (loop with direction = :first + for data-source+description + = (multiple-value-list (%sql-data-sources henv :direction direction)) + while (car data-source+description) + collect data-source+description + do (setf direction :next)) + (%sql-free-environment henv)))) diff --git a/db-odbc/odbc-ff-interface.lisp b/db-odbc/odbc-ff-interface.lisp new file mode 100644 index 0000000..0fa0824 --- /dev/null +++ b/db-odbc/odbc-ff-interface.lisp @@ -0,0 +1,418 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Package: odbc -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-ff-interface.lisp +;;;; Purpose: Function definitions for UFFI interface to ODBC +;;;; Author: Kevin M. Rosenberg +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; and Copyright (C) Paul Meurer 1999 - 2001. All rights reserved. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:odbc) + +(def-foreign-type sql-handle :pointer-void) +(def-foreign-type sql-handle-ptr (* sql-handle)) +(def-foreign-type string-ptr (* :unsigned-char)) +(def-type long-ptr-type (* #.$ODBC-LONG-TYPE)) + +;; ODBC3 +(def-function "SQLAllocHandle" + ((handle-type :short) + (input-handle sql-handle) + (*phenv sql-handle-ptr)) + :module "odbc" + :returning :short) + +;; ODBC3 version of SQLFreeStmt, SQLFreeConnect, and SSQLFreeStmt +(def-function "SQLFreeHandle" + ((handle-type :short) ; HandleType + (input-handle sql-handle)) ; Handle + :module "odbc" + :returning :short) ; RETCODE_SQL_API + + +;; deprecated +(def-function "SQLAllocEnv" + ((*phenv sql-handle-ptr) ; HENV FAR *phenv + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +;; deprecated +(def-function "SQLAllocConnect" + ((henv sql-handle) ; HENV henv + (*phdbc sql-handle-ptr) ; HDBC FAR *phdbc + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLConnect" + ((hdbc sql-handle) ; HDBC hdbc + (*szDSN :cstring) ; UCHAR FAR *szDSN + (cbDSN :short) ; SWORD cbDSN + (*szUID :cstring) ; UCHAR FAR *szUID + (cbUID :short) ; SWORD cbUID + (*szAuthStr :cstring) ; UCHAR FAR *szAuthStr + (cbAuthStr :short) ; SWORD cbAuthStr + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLDriverConnect" + ((hdbc sql-handle) ; HDBC hdbc + (hwnd sql-handle) ; SQLHWND hwnd + (*szConnStrIn :cstring) ; UCHAR FAR *szConnStrIn + (cbConnStrIn :short) ; SWORD cbConnStrIn + (*szConnStrOut string-ptr) ; UCHAR FAR *szConnStrOut + (cbConnStrOutMax :short) ; SWORD cbConnStrOutMax + (*pcbConnStrOut :pointer-void) ; SWORD FAR *pcbConnStrOut + (fDriverCompletion :short) ; UWORD fDriverCompletion + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLDisconnect" + ((hdbc sql-handle)) ; HDBC hdbc + :module "odbc" + :returning :short) ; RETCODE_SQL_API + + +;;deprecated +(def-function "SQLFreeConnect" + ((hdbc sql-handle)) ; HDBC hdbc + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +;; deprecated +(def-function "SQLAllocStmt" + ((hdbc sql-handle) ; HDBC hdbc + (*phstmt sql-handle-ptr) ; HSTMT FAR *phstmt + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLGetInfo" + ((hdbc sql-handle) ; HDBC hdbc + (fInfoType :short) ; UWORD fInfoType + (rgbInfoValue :pointer-void) ; PTR rgbInfoValue + (cbInfoValueMax :short) ; SWORD cbInfoValueMax + (*pcbInfoValue :pointer-void) ; SWORD FAR *pcbInfoValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLPrepare" + ((hstmt sql-handle) ; HSTMT hstmt + (*szSqlStr :cstring) ; UCHAR FAR *szSqlStr + (cbSqlStr :int) ; SDWORD cbSqlStr + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLExecute" + ((hstmt sql-handle) ; HSTMT hstmt + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLExecDirect" + ((hstmt sql-handle) ; HSTMT hstmt + (*szSqlStr :cstring) ; UCHAR FAR *szSqlStr + (cbSqlStr :int) ; SDWORD cbSqlStr + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLFreeStmt" + ((hstmt sql-handle) ; HSTMT hstmt + (fOption :short)) ; UWORD fOption + :module "odbc" + :returning :short) ; RETCODE_SQL_API + + (def-function "SQLCancel" + ((hstmt sql-handle) ; HSTMT hstmt + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLError" + ((henv sql-handle) ; HENV henv + (hdbc sql-handle) ; HDBC hdbc + (hstmt sql-handle) ; HSTMT hstmt + (*szSqlState string-ptr) ; UCHAR FAR *szSqlState + (*pfNativeError (* :int)) ; SDWORD FAR *pfNativeError + (*szErrorMsg string-ptr) ; UCHAR FAR *szErrorMsg + (cbErrorMsgMax :short) ; SWORD cbErrorMsgMax + (*pcbErrorMsg (* :short)) ; SWORD FAR *pcbErrorMsg + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLNumResultCols" + ((hstmt sql-handle) ; HSTMT hstmt + (*pccol (* :short)) ; SWORD FAR *pccol + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLRowCount" + ((hstmt sql-handle) ; HSTMT hstmt + (*pcrow (* :int)) ; SDWORD FAR *pcrow + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLDescribeCol" + ((hstmt sql-handle) ; HSTMT hstmt + (icol :short) ; UWORD icol + (*szColName string-ptr) ; UCHAR FAR *szColName + (cbColNameMax :short) ; SWORD cbColNameMax + (*pcbColName (* :short)) ; SWORD FAR *pcbColName + (*pfSqlType (* :short)) ; SWORD FAR *pfSqlType + (*pcbColDef (* #.$ODBC-ULONG-TYPE)) ; UDWORD FAR *pcbColDef + (*pibScale (* :short)) ; SWORD FAR *pibScale + (*pfNullable (* :short)) ; SWORD FAR *pfNullable + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLColAttributes" + ((hstmt sql-handle) ; HSTMT hstmt + (icol :short) ; UWORD icol + (fDescType :short) ; UWORD fDescType + (rgbDesc string-ptr) ; PTR rgbDesc + (cbDescMax :short) ; SWORD cbDescMax + (*pcbDesc (* :short)) ; SWORD FAR *pcbDesc + (*pfDesc (* :int)) ; SDWORD FAR *pfDesc + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLColumns" + ((hstmt sql-handle) ; HSTMT hstmt + (*szTableQualifier :cstring) ; UCHAR FAR *szTableQualifier + (cbTableQualifier :short) ; SWORD cbTableQualifier + (*szTableOwner :cstring) ; UCHAR FAR *szTableOwner + (cbTableOwner :short) ; SWORD cbTableOwner + (*szTableName :cstring) ; UCHAR FAR *szTableName + (cbTableName :short) ; SWORD cbTableName + (*szColumnName :cstring) ; UCHAR FAR *szColumnName + (cbColumnName :short) ; SWORD cbColumnName + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLBindCol" + ((hstmt sql-handle) ; HSTMT hstmt + (icol :short) ; UWORD icol + (fCType :short) ; SWORD fCType + (rgbValue :pointer-void) ; PTR rgbValue + (cbValueMax :int) ; SDWORD cbValueMax + (*pcbValue (* :int)) ; SDWORD FAR *pcbValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLFetch" + ((hstmt sql-handle) ; HSTMT hstmt + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLTransact" + ((henv sql-handle) ; HENV henv + (hdbc sql-handle) ; HDBC hdbc + (fType :short) ; UWORD fType ($SQL_COMMIT or $SQL_ROLLBACK) + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +;; ODBC 2.0 +(def-function "SQLDescribeParam" + ((hstmt sql-handle) ; HSTMT hstmt + (ipar :short) ; UWORD ipar + (*pfSqlType (* :short)) ; SWORD FAR *pfSqlType + (*pcbColDef (* :unsigned-int)) ; UDWORD FAR *pcbColDef + (*pibScale (* :short)) ; SWORD FAR *pibScale + (*pfNullable (* :short)) ; SWORD FAR *pfNullable + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +;; ODBC 2.0 +(def-function "SQLBindParameter" + ((hstmt sql-handle) ; HSTMT hstmt + (ipar :short) ; UWORD ipar + (fParamType :short) ; SWORD fParamType + (fCType :short) ; SWORD fCType + (fSqlType :short) ; SWORD fSqlType + (cbColDef :int) ; UDWORD cbColDef + (ibScale :short) ; SWORD ibScale + (rgbValue :pointer-void) ; PTR rgbValue + (cbValueMax :int) ; SDWORD cbValueMax + (*pcbValue :pointer-void) ; SDWORD FAR *pcbValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +;; level 1 +(def-function "SQLGetData" + ((hstmt sql-handle) ; HSTMT hstmt + (icol :short) ; UWORD icol + (fCType :short) ; SWORD fCType + (rgbValue :pointer-void) ; PTR rgbValue + (cbValueMax :int) ; SDWORD cbValueMax + (*pcbValue :pointer-void) ; SDWORD FAR *pcbValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLParamData" + ((hstmt sql-handle) ; HSTMT hstmt + (*prgbValue :pointer-void) ; PTR FAR *prgbValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLPutData" + ((hstmt sql-handle) ; HSTMT hstmt + (rgbValue :pointer-void) ; PTR rgbValue + (cbValue :int) ; SDWORD cbValue + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLGetConnectOption" + ((hdbc sql-handle) ; HDBC hdbc + (fOption :short) ; UWORD fOption + (pvParam :pointer-void) ; PTR pvParam + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLSetConnectOption" + ((hdbc sql-handle) ; HDBC hdbc + (fOption :short) ; UWORD fOption + (vParam :int) ; UDWORD vParam + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLSetPos" + ((hstmt sql-handle) ; HSTMT hstmt + (irow :short) ; UWORD irow + (fOption :short) ; UWORD fOption + (fLock :short) ; UWORD fLock + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + + ; level 2 +(def-function "SQLExtendedFetch" + ((hstmt sql-handle) ; HSTMT hstmt + (fFetchType :short) ; UWORD fFetchType + (irow :int) ; SDWORD irow + (*pcrow :pointer-void) ; UDWORD FAR *pcrow + (*rgfRowStatus :pointer-void) ; UWORD FAR *rgfRowStatus + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLDataSources" + ((henv sql-handle) ; HENV henv + (fDirection :short) + (*szDSN string-ptr) ; UCHAR FAR *szDSN + (cbDSNMax :short) ; SWORD cbDSNMax + (*pcbDSN (* :short)) ; SWORD *pcbDSN + (*szDescription string-ptr) ; UCHAR *szDescription + (cbDescriptionMax :short) ; SWORD cbDescriptionMax + (*pcbDescription (* :short)) ; SWORD *pcbDescription + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + +(def-function "SQLFreeEnv" + ((henv sql-handle) ; HSTMT hstmt + ) + :module "odbc" + :returning :short) ; RETCODE_SQL_API + + +;;; foreign type definitions + +;;(defmacro %sql-len-data-at-exec (length) +;; `(- $SQL_LEN_DATA_AT_EXEC_OFFSET ,length)) + + +(def-struct sql-c-time + (hour :short) + (minute :short) + (second :short)) + +(def-struct sql-c-date + (year :short) + (month :short) + (day :short)) + +(def-struct sql-c-timestamp + (year :short) + (month :short) + (day :short) + (hour :short) + (minute :short) + (second :short) + (fraction :int)) + +;;; Added by KMR + +(def-function "SQLSetEnvAttr" + ((henv sql-handle) ; HENV henv + (attr :int) + (*value :pointer-void) + (szLength :int)) + :module "odbc" + :returning :short) + +(def-function "SQLGetEnvAttr" + ((henv sql-handle) ; HENV henv + (attr :int) + (*value :pointer-void) + (szLength :int) + (string-length-ptr (* :int))) + :module "odbc" + :returning :short) + +(def-function "SQLTables" + ((hstmt :pointer-void) + (catalog-name :pointer-void) + (catalog-name-length :short) + (schema-name :pointer-void) + (schema-name-length :short) + (table-name :pointer-void) + (table-name-length :short) + (table-type-name :pointer-void) + (table-type-name-length :short)) + :module "odbc" + :returning :short) + + +(def-function "SQLStatistics" + ((hstmt :pointer-void) + (catalog-name :pointer-void) + (catalog-name-length :short) + (schema-name :pointer-void) + (schema-name-length :short) + (table-name :cstring) + (table-name-length :short) + (unique :short) + (reserved :short)) + :module "odbc" + :returning :short) + + diff --git a/db-odbc/odbc-loader.lisp b/db-odbc/odbc-loader.lisp new file mode 100644 index 0000000..16f1ec1 --- /dev/null +++ b/db-odbc/odbc-loader.lisp @@ -0,0 +1,41 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-loader.sql +;;;; Purpose: ODBC library loader using UFFI +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: April 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:odbc) + +(defparameter *odbc-library-filenames* + '("odbc32" "libodbc" "libiodbc")) + +(defvar *odbc-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the Odbc client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *odbc-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod clsql-sys:database-type-library-loaded ((database-type (eql :odbc))) + *odbc-library-loaded*) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :odbc))) + (clsql-uffi:find-and-load-foreign-library *odbc-library-filenames* + :module "odbc") + (setq *odbc-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :odbc) + + + diff --git a/db-odbc/odbc-package.lisp b/db-odbc/odbc-package.lisp new file mode 100644 index 0000000..40214eb --- /dev/null +++ b/db-odbc/odbc-package.lisp @@ -0,0 +1,69 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-package.lisp +;;;; Purpose: Package definition for low-level ODBC interface +;;;; Author: Kevin M. Rosenberg +;;;; Created: April 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:odbc + (:use #:cl #:uffi) + (:export + #:database-library-loaded + + #:*null* + #:+null-ptr+ + #:+max-precision+ + #:*info-output* + #:*time-format* + #:get-cast-long + #:%free-statement + #:%disconnect + #:%commit + #:%rollback + #:%sql-fetch + #:%sql-cancel + #:db-connect + #:%new-db-connection-handle + #:%new-environment-handle + #:%sql-connect + #:%sql-driver-connect + #:disable-autocommit + #:enable-autocommit + #:%sql-free-environment + #:%sql-data-sources + #:%sql-get-info + #:%sql-param-data + #:%sql-execute + #:%put-str + #:%sql-bind-parameter + #:%sql-prepare + #:sqlfetch + #:%bind-column + #:%allocate-bindings + #:%describe-column + #:%describe-columns + #:read-data + #:read-data-in-chunks + #:query-database + #:%new-statement-handle + #:%sql-exec-direct + #:result-columns-count + #:result-rows-count + #:sql-to-c-type + #:%list-tables + #:%table-statistics + #:%list-data-sources + ) + (:documentation "This is the low-level interface ODBC.")) + diff --git a/db-odbc/odbc-sql.lisp b/db-odbc/odbc-sql.lisp new file mode 100644 index 0000000..b36833e --- /dev/null +++ b/db-odbc/odbc-sql.lisp @@ -0,0 +1,163 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: odbc-sql.cl +;;;; Purpose: Medium-level interface for CLSQL ODBC backend +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(defpackage #:clsql-odbc + (:use #:common-lisp #:clsql-sys) + (:export #:odbc-database) + (:documentation "This is the CLSQL interface to ODBC.")) + +(in-package #:clsql-odbc) + +;; ODBC interface + +(defclass odbc-database (generic-odbc-database) + ()) + +(defclass odbc-postgresql-database (generic-odbc-database + generic-postgresql-database) + ()) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :odbc))) + (check-connection-spec connection-spec database-type + (dsn user password &key connection-string completion window-handle)) + (destructuring-bind (dsn user password &key connection-string completion window-handle) connection-spec + (declare (ignore password connection-string completion window-handle)) + (concatenate 'string dsn "/" user))) + +(defmethod database-connect (connection-spec (database-type (eql :odbc))) + (check-connection-spec connection-spec database-type + (dsn user password &key connection-string completion window-handle)) + (destructuring-bind (dsn user password &key connection-string (completion :no-prompt) window-handle) connection-spec + (handler-case + (let ((db (make-instance 'odbc-database + :name (database-name-from-spec connection-spec :odbc) + :database-type :odbc + :connection-spec connection-spec + :dbi-package (find-package '#:odbc-dbi) + :odbc-conn + (odbc-dbi:connect :user user + :password password + :data-source-name dsn + :connection-string connection-string + :completion completion + :window-handle window-handle)))) + (store-type-of-connected-database db) + ;; Ensure this database type is initialized so can check capabilities of + ;; underlying database + (initialize-database-type :database-type database-type) + (if (eql :postgresql (database-underlying-type db)) + (make-instance 'odbc-postgresql-database + :name (database-name-from-spec connection-spec :odbc) + :database-type :odbc + :connection-spec connection-spec + :dbi-package (find-package '#:odbc-dbi) + :odbc-db-type :postgresql + :odbc-conn (clsql-sys::odbc-conn db)) + db)) + #+ignore + (error () ;; Init or Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :message "Connection failed"))))) + +(defmethod database-underlying-type ((database generic-odbc-database)) + (clsql-sys::database-odbc-db-type database)) + +(defun store-type-of-connected-database (db) + (let* ((odbc-conn (clsql-sys::odbc-conn db)) + (server-name (odbc-dbi::get-odbc-info odbc-conn odbc::$SQL_SERVER_NAME)) + (dbms-name (odbc-dbi::get-odbc-info odbc-conn odbc::$SQL_DBMS_NAME)) + (type + ;; need SERVER-NAME and DBMS-NAME because many drivers mix this up + (cond + ((or (search "postgresql" server-name :test #'char-equal) + (search "postgresql" dbms-name :test #'char-equal)) + (unless (find-package 'clsql-postgresql) + (ignore-errors (asdf:operate 'asdf:load-op 'clsql-postgresql-socket))) + :postgresql) + ((or (search "Microsoft SQL Server" server-name :test #'char-equal) + (search "Microsoft SQL Server" dbms-name :test #'char-equal)) + :mssql) + ((or (search "mysql" server-name :test #'char-equal) + (search "mysql" dbms-name :test #'char-equal)) + (unless (find-package 'clsql-mysql) + ;; ignore errors on platforms where the shared libraries are not available + (ignore-errors (asdf:operate 'asdf:load-op 'clsql-mysql))) + :mysql) + ((or (search "oracle" server-name :test #'char-equal) + (search "oracle" dbms-name :test #'char-equal)) + :oracle)))) + (setf (clsql-sys::database-odbc-db-type db) type))) + + + +(defmethod database-create (connection-spec (type (eql :odbc))) + (declare (ignore connection-spec)) + (warn "Not implemented.")) + +(defmethod database-destroy (connection-spec (type (eql :odbc))) + (declare (ignore connection-spec)) + (warn "Not implemented.")) + +(defmethod database-probe (connection-spec (type (eql :odbc))) + (when (find (car connection-spec) (database-list connection-spec type) + :test #'string-equal) + t)) + +(defmethod database-list (connection-spec (type (eql :odbc))) + (declare (ignore connection-spec)) + (odbc-dbi:list-all-data-sources)) + +(defmethod database-list-indexes ((database odbc-database) + &key (owner nil)) + (let ((result '())) + (dolist (table (database-list-tables database :owner owner) result) + (setq result + (append (database-list-table-indexes table database :owner owner) + result))))) + +(defmethod database-list-table-indexes (table (database odbc-database) + &key (owner nil)) + (declare (ignore owner)) + (multiple-value-bind (rows col-names) + (odbc-dbi:list-table-indexes + table + :db (clsql-sys::odbc-conn database)) + (declare (ignore col-names)) + ;; INDEX_NAME is hard-coded in sixth position by ODBC driver + ;; FIXME: ??? is hard-coded in the fourth position + (do ((results nil) + (loop-rows rows (cdr loop-rows))) + ((null loop-rows) (nreverse results)) + (let* ((row (car loop-rows)) + (col (nth 5 row))) + (unless (or (null col) (find col results :test #'string-equal)) + (push col results)))))) + +;;; Database capabilities + +(defmethod db-backend-has-create/destroy-db? ((db-type (eql :odbc))) + nil) + + +(defmethod database-initialize-database-type ((database-type (eql :odbc))) + ;; nothing to do + t) + +(when (clsql-sys:database-type-library-loaded :odbc) + (clsql-sys:initialize-database-type :database-type :odbc)) diff --git a/db-oracle/Makefile b/db-oracle/Makefile new file mode 100644 index 0000000..f2d791e --- /dev/null +++ b/db-oracle/Makefile @@ -0,0 +1,23 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL Oracle interface +# Author: Kevin M. Rosenberg +# Created: May 2004 +# +# This file, part of CLSQL, is Copyright (c) 2004 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean diff --git a/db-oracle/README b/db-oracle/README new file mode 100644 index 0000000..76b8095 --- /dev/null +++ b/db-oracle/README @@ -0,0 +1,21 @@ +This is the header of the cadabra source file. + +;;;; The original version of this code was copyright (c) 1999-2000 Cadabra Inc. +;;;; It was placed in the public domain by Cadabra in January 2000. +;;;; +;;;; The implementors of the original version were Winton Davies +;;;; and William Newman . + +;;;; known issues: +;;;; * The code will leak C resources if errors occur in the the wrong +;;;; places, since it doesn't wrap its allocation/deallocation +;;;; logic in the necessary EVAL-WHENs to prevent this. (This could be +;;;; easily be an issue for long-running processes which recover from +;;;; database errors instead of simply terminating when they occur. It's +;;;; not an issue for programs which consider database errors so abnormal +;;;; that they die immediately when they encounter one.) +;;;; * Instead of reading Oracle header files automatically, this code +;;;; uses constants, types, and function signatures manually transcribed +;;;; from the Oracle header files. Thus, changes in the header files +;;;; may require manual maintenance of the code. (This version was written +;;;; for Oracle 8.1.5.) diff --git a/db-oracle/foreign-resources.lisp b/db-oracle/foreign-resources.lisp new file mode 100644 index 0000000..3344cb4 --- /dev/null +++ b/db-oracle/foreign-resources.lisp @@ -0,0 +1,57 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +(defparameter *foreign-resource-hash* (make-hash-table :test #'equal)) + +(defstruct (foreign-resource) + (type (error "Missing TYPE.") + :read-only t) + (sizeof (error "Missing SIZEOF.") + :read-only t) + (buffer (error "Missing BUFFER.") + :read-only t) + (in-use nil :type boolean)) + + +(defun %get-resource (type sizeof) + (let ((resources (gethash type *foreign-resource-hash*))) + (car (member-if + #'(lambda (res) + (and (= (foreign-resource-sizeof res) sizeof) + (not (foreign-resource-in-use res)))) + resources)))) + +(defun %insert-foreign-resource (type res) + (let ((resource (gethash type *foreign-resource-hash*))) + (setf (gethash type *foreign-resource-hash*) + (cons res resource)))) + +(defmacro acquire-foreign-resource (type &optional size) + `(let ((res (%get-resource ,type ,size))) + (unless res + (setf res (make-foreign-resource + :type ,type :sizeof ,size + :buffer (uffi:allocate-foreign-object ,type ,size))) + (%insert-foreign-resource ',type res)) + (claim-foreign-resource res))) + +(defun free-foreign-resource (ares) + (setf (foreign-resource-in-use ares) nil) + ares) + +(defun claim-foreign-resource (ares) + (setf (foreign-resource-in-use ares) t) + ares) + + + diff --git a/db-oracle/oracle-api.lisp b/db-oracle/oracle-api.lisp new file mode 100644 index 0000000..1ebb0d9 --- /dev/null +++ b/db-oracle/oracle-api.lisp @@ -0,0 +1,356 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle.lisp +;;;; Purpose: Package definition for CLSQL Oracle interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +;; +;; OCI integer types +;; + +(uffi:def-foreign-type ub2 :unsigned-short) +(uffi:def-foreign-type sb2 :short) +(uffi:def-foreign-type ub4 :unsigned-int) +(uffi:def-foreign-type sb4 :int) +(uffi:def-foreign-type size_t :unsigned-long) + +;; +;; Opaque pointer types +;; + +(uffi:def-foreign-type void-pointer :pointer-void) +(uffi:def-foreign-type oci-env :pointer-void) +(uffi:def-foreign-type oci-server :pointer-void) +(uffi:def-foreign-type oci-error :pointer-void) +(uffi:def-foreign-type oci-svc-ctx :pointer-void) +(uffi:def-foreign-type oci-stmt :pointer-void) + +(uffi:def-pointer-var +null-void-pointer+ + (uffi:make-null-pointer :void)) +(uffi:def-pointer-var +null-void-pointer-pointer+ + (uffi:make-null-pointer :pointer-void)) + +;;; Check an OCI return code for erroricity and signal a reasonably +;;; informative condition if so. +;;; +;;; ERRHP provides an error handle which can be used to find +;;; subconditions; if it's not provided, subcodes won't be checked. +;;; +;;; NULLS-OK says that a NULL-VALUE-RETURNED subcondition condition is +;;; normal and needn't cause any signal. An error handle is required +;;; to detect this subcondition, so it doesn't make sense to set ERRHP +;;; unless NULLS-OK is set. + +(defmacro def-oci-routine ((c-oci-symbol lisp-oci-fn) c-return &rest c-parms) + (let ((ll (mapcar (lambda (x) (declare (ignore x)) (gensym)) c-parms)) + (c-oci-fn (intern (concatenate 'string "%" (symbol-name lisp-oci-fn))))) + `(progn + (declaim (inline ,c-oci-fn ,lisp-oci-fn)) + (uffi:def-function (,c-oci-symbol ,c-oci-fn) + ,c-parms + :returning ,c-return) + (defun ,lisp-oci-fn (,@ll &key database nulls-ok) + (let ((result (,c-oci-fn ,@ll))) + (if (= result #.+oci-success+) + +oci-success+ + (handle-oci-result result database nulls-ok))))))) + + +(defmacro def-raw-oci-routine + ((c-oci-symbol lisp-oci-fn) c-return &rest c-parms) + (let ((ll (mapcar (lambda (x) (declare (ignore x)) (gensym)) c-parms))) + `(let ((%lisp-oci-fn (uffi:def-function (,c-oci-symbol ,(intern (concatenate 'string "%" (symbol-name lisp-oci-fn)))) + ,c-parms + :returning ,c-return))) + (defun ,lisp-oci-fn (,@ll &key database nulls-ok) + (declare (ignore database nulls-ok)) + (funcall %lisp-oci-fn ,@ll))))) + + +(def-oci-routine ("OCIInitialize" oci-initialize) + :int + (mode ub4) ; ub4 + (ctxp :pointer-void) ; dvoid * + (malocfp :pointer-void) ; dvoid *(*) + (ralocfp :pointer-void) ; dvoid *(*) + (mfreefp (* :pointer-void))) ; void *(*) + + +(def-oci-routine ("OCIEnvInit" oci-env-init) + :int + (envpp :pointer-void) ; OCIEnv ** + (mode ub4) ; ub4 + (xtramem-sz size_t) ; size_t + (usermempp (* :pointer-void))) ; dvoid ** + +#-oci7 +(def-oci-routine ("OCIEnvCreate" oci-env-create) + :int + (envhpp (* :pointer-void)) + (mode ub4) + (ctxp :pointer-void) + (malocfp :pointer-void) + (ralocfp :pointer-void) + (mfreefp :pointer-void) + (xtramemsz size_t) + (usrmempp (* :pointer-void))) + +(def-oci-routine ("OCIHandleAlloc" oci-handle-alloc) + :int + (parenth :pointer-void) ; const dvoid * + (hndlpp (* :pointer-void)) ; dvoid ** + (type ub4) ; ub4 + (xtramem_sz size_t) ; size_t + (usrmempp (* :pointer-void))) ; dvoid ** + +(def-oci-routine ("OCIServerAttach" oci-server-attach) + :int + (srvhp :pointer-void) ; oci-server + (errhp :pointer-void) ; oci-error + (dblink :cstring) ; :in + (dblink-len sb4) ; sb4 + (mode ub4)) ; ub4 + + +(def-oci-routine ("OCIHandleFree" oci-handle-free) + :int + (p0 :pointer-void) ;; handle + (p1 ub4)) ;;type + +(def-oci-routine ("OCILogon" oci-logon) + :int + (envhp :pointer-void) ; env + (errhp :pointer-void) ; err + (svchpp (* :pointer-void)) ; svc + (username (* :unsigned-char)) ; username + (uname-len ub4) ; + (passwd (* :unsigned-char)) ; passwd + (password-len ub4) ; + (dsn (* :unsigned-char)) ; datasource + (dsn-len ub4)) ; + +(def-oci-routine ("OCILogoff" oci-logoff) + :int + (p0 :pointer-void) ; svc + (p1 :pointer-void)) ; err + +(declaim (inline oci-error-get)) +(uffi:def-function ("OCIErrorGet" oci-error-get) + ((handlp :pointer-void) + (recordno ub4) + (sqlstate :cstring) + (errcodep (* sb4)) + (bufp (* :unsigned-char)) + (bufsize ub4) + (type ub4)) + :returning :void) + +(def-oci-routine ("OCIStmtPrepare" oci-stmt-prepare) + :int + (stmtp :pointer-void) + (errhp :pointer-void) + (stmt (* :unsigned-char)) + (stmt_len ub4) + (language ub4) + (mode ub4)) + +(def-oci-routine ("OCIStmtExecute" oci-stmt-execute) + :int + (svchp :pointer-void) + (stmtp1 :pointer-void) + (errhp :pointer-void) + (iters ub4) + (rowoff ub4) + (snap_in :pointer-void) + (snap_out :pointer-void) + (mode ub4)) + +(def-raw-oci-routine ("OCIParamGet" oci-param-get) + :int + (hndlp :pointer-void) + (htype ub4) + (errhp :pointer-void) + (parmdpp (* :pointer-void)) + (pos ub4)) + +(def-oci-routine ("OCIAttrGet" oci-attr-get) + :int + (trgthndlp :pointer-void) + (trghndltyp ub4) + (attributep :pointer-void) + (sizep (* ub4)) + (attrtype ub4) + (errhp :pointer-void)) + +(def-oci-routine ("OCIAttrSet" oci-attr-set) + :int + (trgthndlp :pointer-void) + (trgthndltyp ub4 :in) + (attributep :pointer-void) + (size ub4) + (attrtype ub4) + (errhp oci-error)) + +(def-oci-routine ("OCIDefineByPos" oci-define-by-pos) + :int + (stmtp :pointer-void) + (defnpp (* :pointer-void)) + (errhp :pointer-void) + (position ub4) + (valuep :pointer-void) + (value_sz sb4) + (dty ub2) + (indp (* sb2)) + (rlenp (* ub2)) + (rcodep (* ub2)) + (mode ub4)) + +(def-oci-routine ("OCIStmtFetch" oci-stmt-fetch) + :int + (stmthp :pointer-void) + (errhp :pointer-void) + (p2 ub4) + (p3 ub2) + (p4 ub4)) + + +(def-oci-routine ("OCITransStart" oci-trans-start) + :int + (svchp :pointer-void) + (errhp :pointer-void) + (p2 :unsigned-short) + (p3 :unsigned-short)) + +(def-oci-routine ("OCITransCommit" oci-trans-commit) + :int + (svchp :pointer-void) + (errhp :pointer-void) + (p2 :unsigned-short)) + +(def-oci-routine ("OCITransRollback" oci-trans-rollback) + :int + (svchp :pointer-void) + (errhp :pointer-void) + (p2 :unsigned-short)) + + +(def-oci-routine ("OCIServerVersion" oci-server-version) + :int + (handlp :pointer-void) + (errhp :pointer-void) + (bufp (* :unsigned-char)) + (bufsz :int) + (hndltype :short)) + + + +;;; Low-level routines that don't do error checking. They are used +;;; for setting up global environment. + +(uffi:def-function "OCIInitialize" + ((mode ub4) ; ub4 + (ctxp :pointer-void) ; dvoid * + (malocfp :pointer-void) ; dvoid *(*) + (ralocfp :pointer-void) ; dvoid *(*) + (mfreefp (* :pointer-void))) + :returning :int) + +(uffi:def-function "OCIEnvInit" + ((envpp :pointer-void) ; OCIEnv ** + (mode ub4) ; ub4 + (xtramem-sz size_t) ; size_t + (usermempp (* :pointer-void))) + :returning :int) + + +(uffi:def-function "OCIHandleAlloc" + ((parenth :pointer-void) ; const dvoid * + (hndlpp (* :pointer-void)) ; dvoid ** + (type ub4) ; ub4 + (xtramem_sz size_t) ; size_t + (usrmempp (* :pointer-void))) + :returning :int) + +(defstruct oci-handle + (type :unknown) + (pointer (uffi:allocate-foreign-object :pointer-void))) + +(defvar *oci-initialized* nil) +(defvar *oci-env* nil) + +(defvar *oci-handle-types* + '(:error ; error report handle (OCIError) + :service-context ; service context handle (OCISvcCtx) + :statement ; statement (application request) handle (OCIStmt) + :describe ; select list description handle (OCIDescribe) + :server ; server context handle (OCIServer) + :session ; user session handle (OCISession) + :transaction ; transaction context handle (OCITrans) + :complex-object ; complex object retrieval handle (OCIComplexObject) + :security)) ; security handle (OCISecurity) + + + +(defun oci-init (&key (mode +oci-default+)) + (let ((x (OCIInitialize mode +null-void-pointer+ +null-void-pointer+ + +null-void-pointer+ +null-void-pointer-pointer+))) + (if (= x 0) + (let ((env (uffi:allocate-foreign-object :pointer-void))) + (setq *oci-initialized* mode) + (let ((x (OCIEnvInit env +oci-default+ 0 +null-void-pointer+))) + (format t ";; OEI: returned ~d~%" x) + (setq *oci-env* env)))))) + +(defun oci-check-return (value) + (when (= value +oci-invalid-handle+) + (error 'sql-database-error :message "Invalid Handle"))) + +(defun oci-get-handle (&key type) + (if (null *oci-initialized*) + (oci-init)) + (case type + (:error + (let ((ptr (uffi:allocate-foreign-object :pointer-void))) + (let ((x (OCIHandleAlloc + (uffi:deref-pointer *oci-env* void-pointer) + ptr + +oci-default+ + 0 + +null-void-pointer-pointer+))) + (oci-check-return x) + ptr))) + (:service-context + "OCISvcCtx") + (:statement + "OCIStmt") + (:describe + "OCIDescribe") + (:server + "OCIServer") + (:session + "OCISession") + (:transaction + "OCITrans") + (:complex-object + "OCIComplexObject") + (:security + "OCISecurity") + (t + (error 'sql-database-error + :message + (format nil "'~s' is not a valid OCI handle type" type))))) + +(defun oci-environment () + (let ((envhp (oci-get-handle :type :env))) + (oci-env-init envhp 0 0 +null-void-pointer+) + envhp)) diff --git a/db-oracle/oracle-constants.lisp b/db-oracle/oracle-constants.lisp new file mode 100644 index 0000000..9f76323 --- /dev/null +++ b/db-oracle/oracle-constants.lisp @@ -0,0 +1,541 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle-constants.lisp +;;;; Purpose: Constants for CLSQL Oracle interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +(defconstant +oci-default+ #x00) ; default value for parameters and attributes +(defconstant +oci-threaded+ #x01) ; application is in threaded environment +(defconstant +oci-object+ #x02) ; the application is in object environment +(defconstant +oci-non-blocking+ #x04) ; non blocking mode of operation +(defconstant +oci-env-no-mutex+ #x08) ; the environment handle will not be protected by a mutex internally + +;; Handle types + +(defconstant +oci-htype-env+ 1) ; environment handle +(defconstant +oci-htype-error+ 2) ; error handle +(defconstant +oci-htype-svcctx+ 3) ; service handle +(defconstant +oci-htype-stmt+ 4) ; statement handle +(defconstant +oci-htype-bind+ 5) ; bind handle +(defconstant +oci-htype-define+ 6) ; define handle +(defconstant +oci-htype-describe+ 7) ; describe handle +(defconstant +oci-htype-server+ 8) ; server handle +(defconstant +oci-htype-session+ 9) ; authentication handle +(defconstant +oci-htype-trans+ 10) ; transaction handle +(defconstant +oci-htype-complexobject+ 11) ; complex object retrieval handle +(defconstant +oci-htype-security+ 12) ; security handle + +;; Descriptor types + +(defconstant +oci-dtype-lob+ 50) ; lob locator +(defconstant +oci-dtype-snap+ 51) ; snapshot +(defconstant +oci-dtype-rset+ 52) ; result set +(defconstant +oci-dtype-param+ 53) ; parameter descriptor obtained from ocigparm +(defconstant +oci-dtype-rowid+ 54) ; rowid +(defconstant +oci-dtype-complexobjectcomp+ 55) ; complex object retrieval descriptor +(defconstant +oci-dtype-file+ 56) ; File Lob locator +(defconstant +oci-dtype-aqenq-options+ 57) ; enqueue options +(defconstant +oci-dtype-aqdeq-options+ 58) ; dequeue options +(defconstant +oci-dtype-aqmsg-properties+ 59) ; message properties +(defconstant +oci-dtype-aqagent+ 60) ; aq agent + +;; Objectr pointer types + +(defconstant +oci-otype-name+ 1) ; object name +(defconstant +oci-otype-ref+ 2) ; REF to TDO +(defconstant +oci-otype-ptr+ 3) ; PTR to TDO + +;; Attribute types + +(defconstant +oci-attr-fncode+ 1) ; the OCI function code +(defconstant +oci-attr-object+ 2) ; is the environment initialized in object mode +(defconstant +oci-attr-nonblocking-mode+ 3) ; non blocking mode +(defconstant +oci-attr-sqlcode+ 4) ; the SQL verb +(defconstant +oci-attr-env+ 5) ; the environment handle +(defconstant +oci-attr-server+ 6) ; the server handle +(defconstant +oci-attr-session+ 7) ; the user session handle +(defconstant +oci-attr-trans+ 8) ; the transaction handle +(defconstant +oci-attr-row-count+ 9) ; the rows processed so far +(defconstant +oci-attr-sqlfncode+ 10) ; the SQL verb of the statement +(defconstant +oci-attr-prefetch-rows+ 11) ; sets the number of rows to prefetch +(defconstant +oci-attr-nested-prefetch-rows+ 12) ; the prefetch rows of nested table +(defconstant +oci-attr-prefetch-memory+ 13) ; memory limit for rows fetched +(defconstant +oci-attr-nested-prefetch-memory+ 14) ; memory limit for nested rows +(defconstant +oci-attr-char-count+ 15) ; this specifies the bind and define size in characters +(defconstant +oci-attr-pdscl+ 16) ; packed decimal scale +(defconstant +oci-attr-pdfmt+ 17) ; packed decimal format +(defconstant +oci-attr-param-count+ 18) ; number of column in the select list +(defconstant +oci-attr-rowid+ 19) ; the rowid +(defconstant +oci-attr-charset+ 20) ; the character set value +(defconstant +oci-attr-nchar+ 21) ; NCHAR type +(defconstant +oci-attr-username+ 22) ; username attribute +(defconstant +oci-attr-password+ 23) ; password attribute +(defconstant +oci-attr-stmt-type+ 24) ; statement type +(defconstant +oci-attr-internal-name+ 25) ; user friendly global name +(defconstant +oci-attr-external-name+ 26) ; the internal name for global txn +(defconstant +oci-attr-xid+ 27) ; XOPEN defined global transaction id +(defconstant +oci-attr-trans-lock+ 28) ; +(defconstant +oci-attr-trans-name+ 29) ; string to identify a global transaction +(defconstant +oci-attr-heapalloc+ 30) ; memory allocated on the heap +(defconstant +oci-attr-charset-id+ 31) ; Character Set ID +(defconstant +oci-attr-charset-form+ 32) ; Character Set Form +(defconstant +oci-attr-maxdata-size+ 33) ; Maximumsize of data on the server +(defconstant +oci-attr-cache-opt-size+ 34) ; object cache optimal size +(defconstant +oci-attr-cache-max-size+ 35) ; object cache maximum size percentage +(defconstant +oci-attr-pinoption+ 36) ; object cache default pin option +(defconstant +oci-attr-alloc-duration+ 37) ; object cache default allocation duration +(defconstant +oci-attr-pin-duration+ 38) ; object cache default pin duration +(defconstant +oci-attr-fdo+ 39) ; Format Descriptor object attribute +(defconstant +oci-attr-postprocessing-callback+ 40) ; Callback to process outbind data +(defconstant +oci-attr-postprocessing-context+ 41) ; Callback context to process outbind data +(defconstant +oci-attr-rows-returned+ 42) ; Number of rows returned in current iter - for Bind handles +(defconstant +oci-attr-focbk+ 43) ; Failover Callback attribute +(defconstant +oci-attr-in-v8-mode+ 44) ; is the server/service context in V8 mode +(defconstant +oci-attr-lobempty+ 45) ; empty lob ? +(defconstant +oci-attr-sesslang+ 46) ; session language handle + +;; AQ Attribute Types +;; Enqueue Options + +(defconstant +oci-attr-visibility+ 47) ; visibility +(defconstant +oci-attr-relative-msgid+ 48) ; relative message id +(defconstant +oci-attr-sequence-deviation+ 49) ; sequence deviation + +; - Dequeue Options - + ; consumer name +;#define OCI-ATTR-DEQ-MODE 50 +;(defconstant +OCI-ATTR-CONSUMER-NAME 50 + 51) ; dequeue mode +;#define OCI-ATTR-NAVIGATION 52 ; navigation +;#define OCI-ATTR-WAIT 53 ; wait +;#define OCI-ATTR-DEQ-MSGID 54 ; dequeue message id + +; - Message Properties - +(defconstant +OCI-ATTR-PRIORITY+ 55) ; priority +(defconstant +OCI-ATTR-DELAY+ 56) ; delay +(defconstant +OCI-ATTR-EXPIRATION+ 57) ; expiration +(defconstant +OCI-ATTR-CORRELATION+ 58) ; correlation id +(defconstant +OCI-ATTR-ATTEMPTS+ 59) ; # of attempts +(defconstant +OCI-ATTR-RECIPIENT-LIST+ 60) ; recipient list +(defconstant +OCI-ATTR-EXCEPTION-QUEUE+ 61) ; exception queue name +(defconstant +OCI-ATTR-ENQ-TIME+ 62) ; enqueue time (only OCIAttrGet) +(defconstant +OCI-ATTR-MSG-STATE+ 63) ; message state (only OCIAttrGet) + +;; AQ Agent +(defconstant +OCI-ATTR-AGENT-NAME+ 64) ; agent name +(defconstant +OCI-ATTR-AGENT-ADDRESS+ 65) ; agent address +(defconstant +OCI-ATTR-AGENT-PROTOCOL+ 66) ; agent protocol + +;- Server handle - +(defconstant +OCI-ATTR-NATIVE-FDES+ 67) ; native cncxn file desc + +;-Parameter Attribute Types- + +(defconstant +OCI-ATTR-UNK+ 101) ; unknown attribute +(defconstant +OCI-ATTR-NUM-COLS+ 102) ; number of columns +(defconstant +OCI-ATTR-LIST-COLUMNS+ 103) ; parameter of the column list +(defconstant +OCI-ATTR-RDBA+ 104) ; DBA of the segment header +(defconstant +OCI-ATTR-CLUSTERED+ 105) ; whether the table is clustered +(defconstant +OCI-ATTR-PARTITIONED+ 106) ; whether the table is partitioned +(defconstant +OCI-ATTR-INDEX-ONLY+ 107) ; whether the table is index only +(defconstant +OCI-ATTR-LIST-ARGUMENTS+ 108) ; parameter of the argument list +(defconstant +OCI-ATTR-LIST-SUBPROGRAMS+ 109) ; parameter of the subprogram list +(defconstant +OCI-ATTR-REF-TDO+ 110) ; REF to the type descriptor +(defconstant +OCI-ATTR-LINK+ 111) ; the database link name +(defconstant +OCI-ATTR-MIN+ 112) ; minimum value +(defconstant +OCI-ATTR-MAX+ 113) ; maximum value +(defconstant +OCI-ATTR-INCR+ 114) ; increment value +(defconstant +OCI-ATTR-CACHE+ 115) ; number of sequence numbers cached +(defconstant +OCI-ATTR-ORDER+ 116) ; whether the sequence is ordered +(defconstant +OCI-ATTR-HW-MARK+ 117) ; high-water mark +(defconstant +OCI-ATTR-TYPE-SCHEMA+ 118) ; type's schema name +(defconstant +OCI-ATTR-TIMESTAMP+ 119) ; timestamp of the object +(defconstant +OCI-ATTR-NUM-ATTRS+ 120) ; number of sttributes +(defconstant +OCI-ATTR-NUM-PARAMS+ 121) ; number of parameters +(defconstant +OCI-ATTR-OBJID+ 122) ; object id for a table or view +(defconstant +OCI-ATTR-PTYPE+ 123) ; type of info described by +(defconstant +OCI-ATTR-PARAM+ 124) ; parameter descriptor +(defconstant +OCI-ATTR-OVERLOAD-ID+ 125) ; overload ID for funcs and procs +(defconstant +OCI-ATTR-TABLESPACE+ 126) ; table name space +(defconstant +OCI-ATTR-TDO+ 127) ; TDO of a type +(defconstant +OCI-ATTR-PARSE-ERROR-OFFSET+ 128) ; Parse Error offset +;-Credential Types- +(defconstant +OCI-CRED-RDBMS+ 1) ; database username/password +(defconstant +OCI-CRED-EXT+ 2) ; externally provided credentials + +;; Error Return Values- + +(defconstant +oci-continue+ -24200) ; Continue with the body of the OCI function +(defconstant +oci-still-executing+ -3123) ; OCI would block error +(defconstant +oci-invalid-handle+ -2) ; maps to SQL-INVALID-HANDLE +(defconstant +oci-error+ -1) ; maps to SQL-ERROR +(defconstant +oci-success+ 0) ; maps to SQL-SUCCESS of SAG CLI +(defconstant +oci-success-with-info+ 1) ; maps to SQL-SUCCESS-WITH-INFO +(defconstant +oci-need-data+ 99) ; maps to SQL-NEED-DATA +(defconstant +oci-no-data+ 100) ; maps to SQL-NO-DATA + +;; Parsing Syntax Types- + +(defconstant +oci-ntv-syntax+ 1) ; Use what so ever is the native lang of server +(defconstant +oci-v7-syntax+ 2) ; V7 language +(defconstant +oci-v8-syntax+ 3) ; V8 language + +;-Scrollable Cursor Options- + +(defconstant +oci-fetch-next+ #x02) ; next row +(defconstant +oci-fetch-first+ #x04) ; first row of the result set +(defconstant +oci-fetch-last+ #x08) ; the last row of the result set +(defconstant +oci-fetch-prior+ #x10) ; the previous row relative to current +(defconstant +oci-fetch-absolute+ #x20) ; absolute offset from first +(defconstant +oci-fetch-relative+ #x40) ; offset relative to current + +;-Bind and Define Options- + +(defconstant +OCI-SB2-IND-PTR+ #x01) ; unused +(defconstant +OCI-DATA-AT-EXEC+ #x02) ; data at execute time +(defconstant +OCI-DYNAMIC-FETCH+ #x02) ; fetch dynamically +(defconstant +OCI-PIECEWISE+ #x04) ; piecewise DMLs or fetch +;- + +;-Execution Modes- +(defconstant +OCI-BATCH-MODE+ #x01) ; batch the oci statement for execution +(defconstant +OCI-EXACT-FETCH+ #x02) ; fetch the exact rows specified +(defconstant +OCI-KEEP-FETCH-STATE+ #x04) ; unused +(defconstant +OCI-SCROLLABLE-CURSOR+ #x08) ; cursor scrollable +(defconstant +OCI-DESCRIBE-ONLY+ #x10) ; only describe the statement +(defconstant +OCI-COMMIT-ON-SUCCESS+ #x20) ; commit, if successful execution +;- + +;-Authentication Modes- +(defconstant +OCI-MIGRATE+ #x0001) ; migratable auth context +(defconstant +OCI-SYSDBA+ #x0002) ; for SYSDBA authorization +(defconstant +OCI-SYSOPER+ #x0004) ; for SYSOPER authorization +(defconstant +OCI-PRELIM-AUTH+ #x0008) ; for preliminary authorization +;- + +;-Piece Information- +(defconstant +OCI-PARAM-IN+ #x01) ; in parameter +(defconstant +OCI-PARAM-OUT+ #x02) ; out parameter +;- + +;- Transaction Start Flags - +; NOTE: OCI-TRANS-JOIN and OCI-TRANS-NOMIGRATE not supported in 8.0.X +(defconstant +OCI-TRANS-NEW+ #x00000001) ; starts a new transaction branch +(defconstant +OCI-TRANS-JOIN+ #x00000002) ; join an existing transaction +(defconstant +OCI-TRANS-RESUME+ #x00000004) ; resume this transaction +(defconstant +OCI-TRANS-STARTMASK+ #x000000ff) + + +(defconstant +OCI-TRANS-READONLY+ #x00000100) ; starts a readonly transaction +(defconstant +OCI-TRANS-READWRITE+ #x00000200) ; starts a read-write transaction +(defconstant +OCI-TRANS-SERIALIZABLE+ #x00000400) + ; starts a serializable transaction +(defconstant +OCI-TRANS-ISOLMASK+ #x0000ff00) + +(defconstant +OCI-TRANS-LOOSE+ #x00010000) ; a loosely coupled branch +(defconstant +OCI-TRANS-TIGHT+ #x00020000) ; a tightly coupled branch +(defconstant +OCI-TRANS-TYPEMASK+ #x000f0000) ; + +(defconstant +OCI-TRANS-NOMIGRATE+ #x00100000) ; non migratable transaction + +;- + +;- Transaction End Flags - +(defconstant +OCI-TRANS-TWOPHASE+ #x01000000) ; use two phase commit +;- + +;; AQ Constants +;; NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE +;; The following constants must match the PL/SQL dbms-aq constants +;; NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + +; - Visibility flags - +(defconstant +OCI-ENQ-IMMEDIATE+ 1) ; enqueue is an independent transaction +(defconstant +OCI-ENQ-ON-COMMIT+ 2) ; enqueue is part of current transaction + +; - Dequeue mode flags - +(defconstant +OCI-DEQ-BROWSE+ 1) ; read message without acquiring a lock +(defconstant +OCI-DEQ-LOCKED+ 2) ; read and obtain write lock on message +(defconstant +OCI-DEQ-REMOVE+ 3) ; read the message and delete it + +; - Dequeue navigation flags - +(defconstant +OCI-DEQ-FIRST-MSG+ 1) ; get first message at head of queue +(defconstant +OCI-DEQ-NEXT-MSG+ 3) ; next message that is available +(defconstant +OCI-DEQ-NEXT-TRANSACTION+ 2) ; get first message of next txn group + +; - Message states - +(defconstant +OCI-MSG-WAITING+ 1) ; the message delay has not yet completed +(defconstant +OCI-MSG-READY+ 0) ; the message is ready to be processed +(defconstant +OCI-MSG-PROCESSED+ 2) ; the message has been processed +(defconstant +OCI-MSG-EXPIRED+ 3) ; message has moved to exception queue + +; - Sequence deviation - +(defconstant +OCI-ENQ-BEFORE+ 2) ; enqueue message before another message +(defconstant +OCI-ENQ-TOP+ 3) ; enqueue message before all messages + +; - Visibility flags - +(defconstant +OCI-DEQ-IMMEDIATE+ 1) ; dequeue is an independent transaction +(defconstant +OCI-DEQ-ON-COMMIT+ 2) ; dequeue is part of current transaction + +; - Wait - +(defconstant +OCI-DEQ-WAIT-FOREVER+ -1) ; wait forever if no message available +(defconstant +OCI-DEQ-NO-WAIT+ 0) ; do not wait if no message is available + +; - Delay - +(defconstant +OCI-MSG-NO-DELAY+ 0) ; message is available immediately + +;; Expiration +(defconstant +OCI-MSG-NO-EXPIRATION+ -1) ; message will never expire + +;; Describe Handle Parameter Attributes +;; Attributes common to Columns and Stored Procs + +(defconstant +oci-attr-data-size+ 1) ; maximum size of the data +(defconstant +oci-attr-data-type+ 2) ; the sql type of the column/argument +(defconstant +oci-attr-disp-size+ 3) ; the display size +(defconstant +oci-attr-name+ 4) ; the name of the column/argument +(defconstant +oci-attr-precision+ 5) ; precision if number type +(defconstant +oci-attr-scale+ 6) ; scale if number type +(defconstant +oci-attr-is-null+ 7) ; is it null ? +(defconstant +oci-attr-type-name+ 8) + +;; name of the named data type or a package name for package private types + +(defconstant +OCI-ATTR-SCHEMA-NAME+ 9) ; the schema name +(defconstant +OCI-ATTR-SUB-NAME+ 10) ; type name if package private type +(defconstant +OCI-ATTR-POSITION+ 11) ; relative position of col/arg in the list of cols/args + +; complex object retrieval parameter attributes +(defconstant +OCI-ATTR-COMPLEXOBJECTCOMP-TYPE+ 50) ; +(defconstant +OCI-ATTR-COMPLEXOBJECTCOMP-TYPE-LEVEL+ 51) ; +(defconstant +OCI-ATTR-COMPLEXOBJECT-LEVEL+ 52) ; +(defconstant +OCI-ATTR-COMPLEXOBJECT-COLL-OUTOFLINE+ 53) ; + +; Only Columns +(defconstant +OCI-ATTR-DISP-NAME+ 100) ; the display name + +;; stored procs + +(defconstant +OCI-ATTR-OVERLOAD+ 210) ; is this position overloaded +(defconstant +OCI-ATTR-LEVEL+ 211) ; level for structured types +(defconstant +OCI-ATTR-HAS-DEFAULT+ 212) ; has a default value +(defconstant +OCI-ATTR-IOMODE+ 213) ; in, out inout +(defconstant +OCI-ATTR-RADIX+ 214) ; returns a radix +(defconstant +OCI-ATTR-NUM-ARGS+ 215) ; total number of arguments + +;; named type attributes + +(defconstant +oci-attr-typecode+ 216) ; lobject or collection +(defconstant +oci-attr-collection-typecode+ 217) ; varray or nested table +(defconstant +oci-attr-version+ 218) ; user assigned version +(defconstant +oci-attr-is-incomplete-type+ 219) ; is this an incomplete type +(defconstant +oci-attr-is-system-type+ 220) ; a system type +(defconstant +oci-attr-is-predefined-type+ 221) ; a predefined type +(defconstant +oci-attr-is-transient-type+ 222) ; a transient type +(defconstant +oci-attr-is-system-generated-type+ 223) ; system generated type +(defconstant +oci-attr-has-nested-table+ 224) ; contains nested table attr +(defconstant +oci-attr-has-lob+ 225) ; has a lob attribute +(defconstant +oci-attr-has-file+ 226) ; has a file attribute +(defconstant +oci-attr-collection-element+ 227) ; has a collection attribute +(defconstant +oci-attr-num-type-attrs+ 228) ; number of attribute types +(defconstant +oci-attr-list-type-attrs+ 229) ; list of type attributes +(defconstant +oci-attr-num-type-methods+ 230) ; number of type methods +(defconstant +oci-attr-list-type-methods+ 231) ; list of type methods +(defconstant +oci-attr-map-method+ 232) ; map method of type +(defconstant +oci-attr-order-method+ 233) ; order method of type + +; only collection element +(defconstant +OCI-ATTR-NUM-ELEMS+ 234) ; number of elements + +; only type methods +(defconstant +OCI-ATTR-ENCAPSULATION+ 235) ; encapsulation level +(defconstant +OCI-ATTR-IS-SELFISH+ 236) ; method selfish +(defconstant +OCI-ATTR-IS-VIRTUAL+ 237) ; virtual +(defconstant +OCI-ATTR-IS-INLINE+ 238) ; inline +(defconstant +OCI-ATTR-IS-CONSTANT+ 239) ; constant +(defconstant +OCI-ATTR-HAS-RESULT+ 240) ; has result +(defconstant +OCI-ATTR-IS-CONSTRUCTOR+ 241) ; constructor +(defconstant +OCI-ATTR-IS-DESTRUCTOR+ 242) ; destructor +(defconstant +OCI-ATTR-IS-OPERATOR+ 243) ; operator +(defconstant +OCI-ATTR-IS-MAP+ 244) ; a map method +(defconstant +OCI-ATTR-IS-ORDER+ 245) ; order method +(defconstant +OCI-ATTR-IS-RNDS+ 246) ; read no data state method +(defconstant +OCI-ATTR-IS-RNPS+ 247) ; read no process state +(defconstant +OCI-ATTR-IS-WNDS+ 248) ; write no data state method +(defconstant +OCI-ATTR-IS-WNPS+ 249) ; write no process state + +; describing public objects +(defconstant +OCI-ATTR-DESC-PUBLIC+ 250) ; public object +;- + +;-OCIPasswordChange- +(defconstant +OCI-AUTH+ #x08) ; Change the password but do not login + + +;-Other Constants- +(defconstant +OCI-MAX-FNS+ 100) ; max number of OCI Functions +(defconstant +OCI-SQLSTATE-SIZE+ 5) ; +(defconstant +OCI-ERROR-MAXMSG-SIZE+ 1024) ; max size of an error message +;; (defconstant +OCI-LOBMAXSIZE+ 4MAXVAL) ; maximum lob data size +(defconstant +OCI-ROWID-LEN+ 23) ; +;- + +;- Fail Over Events - +(defconstant +OCI-FO-END+ #x00000001) ; +(defconstant +OCI-FO-ABORT+ #x00000002) ; +(defconstant +OCI-FO-REAUTH+ #x00000004) ; +(defconstant +OCI-FO-BEGIN+ #x00000008) ; +(defconstant +OCI-FO-ERROR+ #x00000010) ; +;- + +;- Fail Over Types - +(defconstant +OCI-FO-NONE+ #x00000001) ; +(defconstant +OCI-FO-SESSION+ #x00000002) ; +(defconstant +OCI-FO-SELECT+ #x00000004) ; +(defconstant +OCI-FO-TXNAL+ #x00000008) ; +;- + +;-Function Codes- +(defconstant +OCI-FNCODE-INITIALIZE+ 1) ; OCIInitialize +(defconstant +OCI-FNCODE-HANDLEALLOC+ 2) ; OCIHandleAlloc +(defconstant +OCI-FNCODE-HANDLEFREE+ 3) ; OCIHandleFree +(defconstant +OCI-FNCODE-DESCRIPTORALLOC+ 4) ; OCIDescriptorAlloc +(defconstant +OCI-FNCODE-DESCRIPTORFREE+ 5) ; OCIDescriptorFree +(defconstant +OCI-FNCODE-ENVINIT+ 6) ; OCIEnvInit +(defconstant +OCI-FNCODE-SERVERATTACH+ 7) ; OCIServerAttach +(defconstant +OCI-FNCODE-SERVERDETACH+ 8) ; OCIServerDetach +; unused 9 +(defconstant +OCI-FNCODE-SESSIONBEGIN+ 10) ; OCISessionBegin +(defconstant +OCI-FNCODE-SESSIONEND+ 11) ; OCISessionEnd +(defconstant +OCI-FNCODE-PASSWORDCHANGE+ 12) ; OCIPasswordChange +(defconstant +OCI-FNCODE-STMTPREPARE+ 13) ; OCIStmtPrepare + ; unused 14- 16 +(defconstant +OCI-FNCODE-BINDDYNAMIC+ 17) ; OCIBindDynamic +(defconstant +OCI-FNCODE-BINDOBJECT+ 18) ; OCIBindObject + ; 19 unused +(defconstant +OCI-FNCODE-BINDARRAYOFSTRUCT+ 20) ; OCIBindArrayOfStruct +(defconstant +OCI-FNCODE-STMTEXECUTE+ 21) ; OCIStmtExecute + ; unused 22-24 +(defconstant +OCI-FNCODE-DEFINEOBJECT+ 25) ; OCIDefineObject +(defconstant +OCI-FNCODE-DEFINEDYNAMIC+ 26) ; OCIDefineDynamic +(defconstant +OCI-FNCODE-DEFINEARRAYOFSTRUCT+ 27) ; OCIDefineArrayOfStruct +(defconstant +OCI-FNCODE-STMTFETCH+ 28) ; OCIStmtFetch +(defconstant +OCI-FNCODE-STMTGETBIND+ 29) ; OCIStmtGetBindInfo + ; 30, 31 unused +(defconstant +OCI-FNCODE-DESCRIBEANY+ 32) ; OCIDescribeAny +(defconstant +OCI-FNCODE-TRANSSTART+ 33) ; OCITransStart +(defconstant +OCI-FNCODE-TRANSDETACH+ 34) ; OCITransDetach +(defconstant +OCI-FNCODE-TRANSCOMMIT+ 35) ; OCITransCommit + ; 36 unused +(defconstant +OCI-FNCODE-ERRORGET+ 37) ; OCIErrorGet +(defconstant +OCI-FNCODE-LOBOPENFILE+ 38) ; OCILobFileOpen +(defconstant +OCI-FNCODE-LOBCLOSEFILE+ 39) ; OCILobFileClose + ; 40 was LOBCREATEFILE, unused + ; 41 was OCILobFileDelete, unused +(defconstant +OCI-FNCODE-LOBCOPY+ 42) ; OCILobCopy +(defconstant +OCI-FNCODE-LOBAPPEND+ 43) ; OCILobAppend +(defconstant +OCI-FNCODE-LOBERASE+ 44) ; OCILobErase +(defconstant +OCI-FNCODE-LOBLENGTH+ 45) ; OCILobGetLength +(defconstant +OCI-FNCODE-LOBTRIM+ 46) ; OCILobTrim +(defconstant +OCI-FNCODE-LOBREAD+ 47) ; OCILobRead +(defconstant +OCI-FNCODE-LOBWRITE+ 48) ; OCILobWrite + ; 49 unused +(defconstant +OCI-FNCODE-SVCCTXBREAK+ 50) ; OCIBreak +(defconstant +OCI-FNCODE-SERVERVERSION+ 51) ; OCIServerVersion +; unused 52, 53 +(defconstant +OCI-FNCODE-ATTRGET+ 54) ; OCIAttrGet +(defconstant +OCI-FNCODE-ATTRSET+ 55) ; OCIAttrSet +(defconstant +OCI-FNCODE-PARAMSET+ 56) ; OCIParamSet +(defconstant +OCI-FNCODE-PARAMGET+ 57) ; OCIParamGet +(defconstant +OCI-FNCODE-STMTGETPIECEINFO+ 58) ; OCIStmtGetPieceInfo +(defconstant +OCI-FNCODE-LDATOSVCCTX+ 59) ; OCILdaToSvcCtx + ; 60 unused +(defconstant +OCI-FNCODE-STMTSETPIECEINFO+ 61) ; OCIStmtSetPieceInfo +(defconstant +OCI-FNCODE-TRANSFORGET+ 62) ; OCITransForget +(defconstant +OCI-FNCODE-TRANSPREPARE+ 63) ; OCITransPrepare +(defconstant +OCI-FNCODE-TRANSROLLBACK+ 64) ; OCITransRollback +(defconstant +OCI-FNCODE-DEFINEBYPOS+ 65) ; OCIDefineByPos +(defconstant +OCI-FNCODE-BINDBYPOS+ 66) ; OCIBindByPos +(defconstant +OCI-FNCODE-BINDBYNAME+ 67) ; OCIBindByName +(defconstant +OCI-FNCODE-LOBASSIGN+ 68) ; OCILobAssign +(defconstant +OCI-FNCODE-LOBISEQUAL+ 69) ; OCILobIsEqual +(defconstant +OCI-FNCODE-LOBISINIT+ 70) ; OCILobLocatorIsInit +; 71 was lob locator size in beta2 +(defconstant +OCI-FNCODE-LOBENABLEBUFFERING+ 71) ; OCILobEnableBuffering +(defconstant +OCI-FNCODE-LOBCHARSETID+ 72) ; OCILobCharSetID +(defconstant +OCI-FNCODE-LOBCHARSETFORM+ 73) ; OCILobCharSetForm +(defconstant +OCI-FNCODE-LOBFILESETNAME+ 74) ; OCILobFileSetName +(defconstant +OCI-FNCODE-LOBFILEGETNAME+ 75) ; OCILobFileGetName +(defconstant +OCI-FNCODE-LOGON+ 76) ; OCILogon +(defconstant +OCI-FNCODE-LOGOFF+ 77) ; OCILogoff +(defconstant +OCI-FNCODE-LOBDISABLEBUFFERING+ 78) ; OCILobDisableBuffering +(defconstant +OCI-FNCODE-LOBFLUSHBUFFER+ 79) ; OCILobFlushBuffer +(defconstant +OCI-FNCODE-LOBLOADFROMFILE+ 80) ; OCILobLoadFromFile + + +;- + +;- FILE open modes - +(defconstant +OCI-FILE-READONLY+ 1) ; readonly mode open for FILE types +;- + +;- LOB Buffering Flush Flags - +(defconstant +OCI-LOB-BUFFER-FREE+ 1) ; +(defconstant +OCI-LOB-BUFFER-NOFREE+ 2) ; +;- + +;- OCI Statement Types - + +(defconstant +oci-stmt-select+ 1) ; select statement +(defconstant +oci-stmt-update+ 2) ; update statement +(defconstant +oci-stmt-delete+ 3) ; delete statement +(defconstant +oci-stmt-insert+ 4) ; insert statement +(defconstant +oci-stmt-create+ 5) ; create statement +(defconstant +oci-stmt-drop+ 6) ; drop statement +(defconstant +oci-stmt-alter+ 7) ; alter statement +(defconstant +oci-stmt-begin+ 8) ; begin ... (pl/sql statement) +(defconstant +oci-stmt-declare+ 9) ; declare .. (pl/sql statement ) +;- + +;- OCI Parameter Types - +(defconstant +OCI-PTYPE-UNK+ 0) ; unknown +(defconstant +OCI-PTYPE-TABLE+ 1) ; table +(defconstant +OCI-PTYPE-VIEW+ 2) ; view +(defconstant +OCI-PTYPE-PROC+ 3) ; procedure +(defconstant +OCI-PTYPE-FUNC+ 4) ; function +(defconstant +OCI-PTYPE-PKG+ 5) ; package +(defconstant +OCI-PTYPE-TYPE+ 6) ; user-defined type +(defconstant +OCI-PTYPE-SYN+ 7) ; synonym +(defconstant +OCI-PTYPE-SEQ+ 8) ; sequence +(defconstant +OCI-PTYPE-COL+ 9) ; column +(defconstant +OCI-PTYPE-ARG+ 10) ; argument +(defconstant +OCI-PTYPE-LIST+ 11) ; list +(defconstant +OCI-PTYPE-TYPE-ATTR+ 12) ; user-defined type's attribute +(defconstant +OCI-PTYPE-TYPE-COLL+ 13) ; collection type's element +(defconstant +OCI-PTYPE-TYPE-METHOD+ 14) ; user-defined type's method +(defconstant +OCI-PTYPE-TYPE-ARG+ 15) ; user-defined type method's argument +(defconstant +OCI-PTYPE-TYPE-RESULT+ 16) ; user-defined type method's result +;- + +;- OCI List Types - +(defconstant +OCI-LTYPE-UNK+ 0) ; unknown +(defconstant +OCI-LTYPE-COLUMN+ 1) ; column list +(defconstant +OCI-LTYPE-ARG-PROC+ 2) ; procedure argument list +(defconstant +OCI-LTYPE-ARG-FUNC+ 3) ; function argument list +(defconstant +OCI-LTYPE-SUBPRG+ 4) ; subprogram list +(defconstant +OCI-LTYPE-TYPE-ATTR+ 5) ; type attribute +(defconstant +OCI-LTYPE-TYPE-METHOD+ 6) ; type method +(defconstant +OCI-LTYPE-TYPE-ARG-PROC+ 7) ; type method w/o result argument list +(defconstant +OCI-LTYPE-TYPE-ARG-FUNC+ 8) ; type method w/result argument list + +;; typecodes + diff --git a/db-oracle/oracle-loader.lisp b/db-oracle/oracle-loader.lisp new file mode 100644 index 0000000..f1493d3 --- /dev/null +++ b/db-oracle/oracle-loader.lisp @@ -0,0 +1,53 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle-loader.lisp +;;;; Purpose: Foreign library loader for CLSQL Oracle interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +(defparameter *oracle-home* + (let ((oracle-home (getenv "ORACLE_HOME"))) + (when oracle-home + (parse-namestring (concatenate 'string oracle-home "/")))) + "Pathname of ORACLE_HOME as set in user environment.") + +(defparameter *oracle-client-library-filenames* + (list "libclntsh" "oci")) + +(defvar *oracle-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the Oracle client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *oracle-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod clsql-sys:database-type-library-loaded ((database-type (eql :oracle))) + *oracle-library-loaded*) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :oracle))) + (when *oracle-home* + (dolist (dir-name '("lib" "bin")) + (dolist (lib-name '("libclntsh" "oci")) + (clsql:push-library-path + (make-pathname :name lib-name + :directory (append (pathname-directory *oracle-home*) + (list dir-name))))))) + + (clsql-uffi:find-and-load-foreign-library *oracle-client-library-filenames* + :module "clsql-oracle" + :supporting-libraries *oracle-supporting-libraries*) + (setq *oracle-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :oracle) + + diff --git a/db-oracle/oracle-objects.lisp b/db-oracle/oracle-objects.lisp new file mode 100644 index 0000000..3e5a6b7 --- /dev/null +++ b/db-oracle/oracle-objects.lisp @@ -0,0 +1,128 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle-objects.lisp +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +(defmethod database-get-type-specifier (type args database (db-type (eql :oracle))) + (declare (ignore type args database)) + (format nil "VARCHAR2(~D)" *default-string-length*)) + +(defmethod database-get-type-specifier ((type (eql 'integer)) args + database (db-type (eql :oracle))) + (declare (ignore database)) + (if args + (format nil "NUMBER(~A,~A)" + (or (first args) 38) (or (second args) 0)) + "INTEGER")) + +(defmethod database-get-type-specifier ((type (eql 'bigint)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "CHAR(20)") + +(defmethod database-get-type-specifier ((type (eql 'universal-time)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "CHAR(20)") + +(defmethod database-get-type-specifier ((type (eql 'string)) args + database (db-type (eql :oracle))) + (declare (ignore database)) + (if args + (format nil "CHAR(~A)" (car args)) + (format nil "VARCHAR2(~D)" *default-string-length*))) + +(defmethod database-get-type-specifier ((type (eql 'varchar)) args + database (db-type (eql :oracle))) + (declare (ignore database)) + (if args + (format nil "VARCHAR2(~A)" (car args)) + (format nil "VARCHAR2(~D)" *default-string-length*))) + +(defmethod database-get-type-specifier ((type (eql 'float)) args + database (db-type (eql :oracle))) + (declare (ignore database)) + (if args + (format nil "NUMBER(~A,~A)" (or (first args) 38) (or (second args) 38)) + "DOUBLE PRECISION")) + +(defmethod database-get-type-specifier ((type (eql 'long-float)) args + database (db-type (eql :oracle))) + (declare (ignore database)) + (if args + (format nil "NUMBER(~A,~A)" + (or (first args) 38) (or (second args) 38)) + "DOUBLE PRECISION")) + +(defmethod database-get-type-specifier ((type (eql 'boolean)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "CHAR(1)") + +(defmethod database-get-type-specifier ((type (eql 'generalized-boolean)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "CHAR(1)") + +(defmethod read-sql-value (val type + database (db-type (eql :oracle))) + ;;(format t "value is \"~A\" of type ~A~%" val (type-of val)) + (declare (ignore type database)) + (etypecase val + (string + (read-from-string val)) + (symbol + nil))) + +(defmethod read-sql-value (val (type (eql 'integer)) + database (db-type (eql :oracle))) + (declare (ignore database)) + val) + +(defmethod read-sql-value (val (type (eql 'float)) + database (db-type (eql :oracle))) + (declare (ignore database)) + val) + +(defmethod read-sql-value (val (type (eql 'boolean)) + database (db-type (eql :oracle))) + (declare (ignore database)) + (when (char-equal #\t (schar val 0)) + t)) + +(defmethod read-sql-value (val (type (eql 'generalized-boolean)) + database (db-type (eql :oracle))) + (declare (ignore database)) + (when (char-equal #\t (schar val 0)) + t)) + +(defmethod read-sql-value (val (type (eql 'bigint)) + database (db-type (eql :oracle))) + (declare (ignore database)) + (parse-integer val)) + +(defmethod read-sql-value (val (type (eql 'universal-time)) + database (db-type (eql :oracle))) + (declare (ignore database)) + (parse-integer val)) + + +(defmethod database-get-type-specifier ((type (eql 'wall-time)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "DATE") + +(defmethod database-get-type-specifier ((type (eql 'duration)) args + database (db-type (eql :oracle))) + (declare (ignore args database)) + "NUMBER(38)") diff --git a/db-oracle/oracle-package.lisp b/db-oracle/oracle-package.lisp new file mode 100644 index 0000000..d5c229f --- /dev/null +++ b/db-oracle/oracle-package.lisp @@ -0,0 +1,23 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle-package.cl +;;;; Purpose: Package definition for CLSQL Oracle interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-oracle + (:use #:common-lisp #:clsql-sys #:clsql-uffi) + (:export #:oracle-database + #:*oracle-server-version* + #:*oracle-so-load-path* + #:*oracle-so-libraries*) + (:documentation "This is the CLSQL interface to Oracle.")) diff --git a/db-oracle/oracle-sql.lisp b/db-oracle/oracle-sql.lisp new file mode 100644 index 0000000..9460011 --- /dev/null +++ b/db-oracle/oracle-sql.lisp @@ -0,0 +1,1074 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: oracle-sql.lisp +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-oracle) + +(defmethod database-initialize-database-type ((database-type (eql :oracle))) + t) + +;;;; arbitrary parameters, tunable for performance or other reasons + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defconstant +errbuf-len+ 512 + "the number of characters that we allocate for an error message buffer") + (defconstant +n-buf-rows+ 200 + "the number of table rows that we buffer at once when reading a table. +CMUCL has a compiled-in limit on how much C data can be allocated +(through malloc() and friends) at any given time, typically 8 Mb. +Setting this constant to a moderate value should make it less +likely that we'll have to worry about the CMUCL limit.")) + + +(uffi:def-type vp-type :pointer-void) +(uffi:def-type vpp-type (* :pointer-void)) + +(defmacro deref-vp (foreign-object) + `(the vp-type (uffi:deref-pointer (the vpp-type ,foreign-object) :pointer-void))) + +(uffi:def-pointer-var +unsigned-char-null-pointer+ + (uffi:make-null-pointer :unsigned-char)) +(uffi:def-pointer-var +unsigned-short-null-pointer+ + (uffi:make-null-pointer :unsigned-short)) +(uffi:def-pointer-var +unsigned-int-null-pointer+ + (uffi:make-null-pointer :unsigned-int)) + +;; constants - from OCI? + +(defconstant +var-not-in-list+ 1007) +(defconstant +no-data-found+ 1403) +(defconstant +null-value-returned+ 1405) +(defconstant +field-truncated+ 1406) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defconstant SQLT-NUMBER 2) + (defconstant SQLT-INT 3) + (defconstant SQLT-FLT 4) + (defconstant SQLT-STR 5) + (defconstant SQLT-DATE 12)) + +;;; Note that despite the suggestive class name (and the way that the +;;; *DEFAULT-DATABASE* variable holds an object of this class), a DB +;;; object is not actually a database but is instead a connection to a +;;; database. Thus, there's no obstacle to having any number of DB +;;; objects referring to the same database. + +(uffi:def-type pointer-pointer-void (* :pointer-void)) + +(defclass oracle-database (database) ; was struct db + ((envhp + :reader envhp + :initarg :envhp + :type pointer-pointer-void + :documentation + "OCI environment handle") + (errhp + :reader errhp + :initarg :errhp + :type pointer-pointer-void + :documentation + "OCI error handle") + (svchp + :reader svchp + :initarg :svchp + :type pointer-pointer-void + :documentation + "OCI service context handle") + (data-source-name + :initarg :dsn + :initform nil + :documentation + "optional data source name (used only for debugging/printing)") + (user + :initarg :user + :reader user + :type string + :documentation + "the \"user\" value given when data source connection was made") + (date-format + :initarg :date-format + :reader date-format + :initform "YYYY-MM-DD HH24:MI:SS\".0\"") + (date-format-length + :type number + :documentation + "Each database connection can be configured with its own date +output format. In order to extract date strings from output buffers +holding multiple date strings in fixed-width fields, we need to know +the length of that format.") + (server-version + :type (or null string) + :initarg :server-version + :reader server-version + :documentation + "Version string of Oracle server.") + (major-server-version + :type (or null fixnum) + :initarg :major-server-version + :reader major-server-version + :documentation + "The major version number of the Oracle server, should be 8, 9, or 10"))) + +;;; Handle a non-successful result from an OCI function. +(defun handle-oci-result (result database nulls-ok) + (case result + (#.+oci-success+ + +oci-success+) + (#.+oci-error+ + (handle-oci-error :database database :nulls-ok nulls-ok)) + (#.+oci-no-data+ + (error 'sql-database-error :message "OCI No Data Found")) + (#.+oci-success-with-info+ + (error 'sql-database-error :message "internal error: unexpected +oci-success-with-info")) + (#.+oci-invalid-handle+ + (error 'sql-database-error :message "OCI Invalid Handle")) + (#.+oci-need-data+ + (error 'sql-database-error :message "OCI Need Data")) + (#.+oci-still-executing+ + (error 'sql-temporary-error :message "OCI Still Executing")) + (#.+oci-continue+ + (error 'sql-database-error :message "OCI Continue")) + (1804 + (error 'sql-database-error :message "Check ORACLE_HOME and NLS settings.")) + (t + (error 'sql-database-error + :message + (format nil "OCI unknown error, code=~A" result))))) + +;;; Handle the messy case of return code=+oci-error+, querying the +;;; system for subcodes and reporting them as appropriate. ERRHP and +;;; NULLS-OK are as in the OERR function. + +(defun handle-oci-error (&key database nulls-ok) + (cond + (database + (with-slots (errhp) database + (let ((errcode (uffi:allocate-foreign-object 'sb4)) + (errbuf (uffi:allocate-foreign-string #.+errbuf-len+))) + ;; ensure errbuf empty string + (setf (uffi:deref-array errbuf '(:array :unsigned-char) 0) + (uffi:ensure-char-storable (code-char 0))) + (setf (uffi:deref-pointer errcode 'sb4) 0) + + (uffi:with-cstring (sqlstate nil) + (oci-error-get (deref-vp errhp) 1 + sqlstate + errcode + (uffi:char-array-to-pointer errbuf) + +errbuf-len+ +oci-htype-error+)) + (let ((subcode (uffi:deref-pointer errcode 'sb4)) + (errstr (uffi:convert-from-foreign-string + errbuf + :encoding (when database (encoding database))))) + (uffi:free-foreign-object errcode) + (uffi:free-foreign-object errbuf) + (unless (and nulls-ok (= subcode +null-value-returned+)) + (error 'sql-database-error + :database database + :error-id subcode + :message errstr)))))) + (nulls-ok + (error 'sql-database-error + :database database + :message "can't handle NULLS-OK without ERRHP")) + (t + (error 'sql-database-error + :database database + :message "OCI Error (and no ERRHP available to find subcode)")))) + +;;; Require an OCI success code. +;;; +;;; (The ordinary OCI error reporting mechanisms uses a fair amount of +;;; machinery (environments and other handles). In order to get to +;;; where we can use these mechanisms, we have to be able to allocate +;;; the machinery. The functions for allocating the machinery can +;;; return errors (e.g. out of memory) but shouldn't. Wrapping this function +;;; around function calls to such have-to-succeed functions enforces +;;; this condition.) + +(defun osucc (code) + (declare (type fixnum code)) + (unless (= code +oci-success+) + (error 'sql-database-error + :message (format nil "unexpected OCI failure, code=~S" code)))) + + +;;; Enabling this can be handy for low-level debugging. +#+nil +(progn + (trace #-oci7 oci-env-create oci-initialize oci-handle-alloc oci-logon + oci-error-get oci-stmt-prepare oci-stmt-execute + oci-param-get oci-logon oci-attr-get oci-define-by-pos oci-stmt-fetch) + (setf debug::*debug-print-length* nil)) + + +;; Return the INDEXth string of the OCI array, represented as Lisp +;; SIMPLE-STRING. SIZE is the size of the fixed-width fields used by +;; Oracle to store strings within the array. + +(uffi:def-type string-pointer (* :unsigned-char)) + +(defun deref-oci-string (arrayptr string-index size encoding) + (declare (type string-pointer arrayptr)) + (declare (type (mod #.+n-buf-rows+) string-index)) + (declare (type (and unsigned-byte fixnum) size)) + (let ((str (uffi:convert-from-foreign-string + (uffi:make-pointer + (+ (uffi:pointer-address arrayptr) (* string-index size)) + :unsigned-char) + :encoding encoding))) + (if (string-equal str "NULL") nil str))) + +;; the OCI library, part Z: no-longer used logic to convert from +;; Oracle's binary date representation to Common Lisp's native date +;; representation + +#+nil +(defvar +oci-date-bytes+ 7) + +;;; Return the INDEXth date in the OCI array, represented as +;;; a Common Lisp "universal time" (i.e. seconds since 1900). + +#+nil +(defun deref-oci-date (arrayptr index) + (oci-date->universal-time (uffi:pointer-address + (uffi:deref-array arrayptr + '(:array :unsigned-char) + (* index +oci-date-bytes+))))) +#+nil +(defun oci-date->universal-time (oci-date) + (declare (type (alien (* :unsigned-char)) oci-date)) + (flet (;; a character from OCI-DATE, interpreted as an unsigned byte + (ub (i) + (declare (type (mod #.+oci-date-bytes+) i)) + (mod (uffi:deref-array oci-date string-array i) 256))) + (let* ((century (* (- (ub 0) 100) 100)) + (year (+ century (- (ub 1) 100))) + (month (ub 2)) + (day (ub 3)) + (hour (1- (ub 4))) + (minute (1- (ub 5))) + (second (1- (ub 6)))) + (encode-universal-time second minute hour day month year)))) + + +(defmethod database-list-tables ((database oracle-database) &key owner) + (let ((query + (cond ((null owner) + "select table_name from user_tables") + ((eq owner :all) + "select table_name from all_tables") + (t + (format nil + "select user_tables.table_name from user_tables,all_tables where user_tables.table_name=all_tables.table_name and all_tables.owner='~:@(~A~)'" + owner))))) + (mapcar #'car (database-query query database nil nil)))) + + +(defmethod database-list-views ((database oracle-database) &key owner) + (let ((query + (cond ((null owner) + "select view_name from user_views") + ((eq owner :all) + "select view_name from all_views") + (t + (format nil + "select user_views.view_name from user_views,all_views where user_views.view_name=all_views.view_name and all_views.owner='~:@(~A~)'" + owner))))) + (mapcar #'car + (database-query query database nil nil)))) + +(defmethod database-list-indexes ((database oracle-database) + &key (owner nil)) + (let ((query + (cond ((null owner) + "select index_name from user_indexes") + ((eq owner :all) + "select index_name from all_indexes") + (t (format nil + "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'" + owner))))) + (mapcar #'car (database-query query database nil nil)))) + +(defmethod database-list-table-indexes (table (database oracle-database) + &key (owner nil)) + (let ((query + (cond ((null owner) + (format nil "select index_name from user_indexes where table_name='~A'" + table)) + ((eq owner :all) + (format nil "select index_name from all_indexes where table_name='~A'" + table)) + (t + (format nil + "select user_indexes.index_name from user_indexes,all_indexes where user_indexes.table_name='~A' and user_indexes.index_name=all_indexes.index_name and all_indexes.owner='~:@(~A~)'" + table owner))))) + (mapcar #'car (database-query query database nil nil)))) + + +(defmethod database-list-attributes (table (database oracle-database) &key owner) + (let ((query + (cond ((null owner) + (format nil "select column_name from user_tab_columns where table_name='~A'" + table)) + ((eq owner :all) + (format nil "select column_name from all_tab_columns where table_name='~A'" + table)) + (t + (format nil + "select user_tab_columns.column_name from user_tab_columns,all_tables where user_tab_columns.table_name='~A' and all_tables.table_name=user_tab_columns.table_name and all_tables.owner='~:@(~A~)'" + table owner))))) + (mapcar #'car (database-query query database nil nil)))) + +(defmethod database-attribute-type (attribute (table string) + (database oracle-database) + &key (owner nil)) + (let ((query + (cond ((null owner) + (format nil + "select data_type,data_length,data_scale,nullable from user_tab_columns where table_name='~A' and column_name='~A'" + table attribute)) + ((eq owner :all) + (format nil + "select data_type,data_length,data_scale,nullable from all_tab_columns where table_name='~A' and column_name='~A'" + table attribute)) + (t + (format nil + "select data_type,data_length,data_scale,nullable from user_tab_columns,all_tables where user_tab_columns.table_name='~A' and column_name='~A' and all_tables.table_name=user_tab_columns.table_name and all_tables.owner='~:@(~A~)'" + table attribute owner))))) + (destructuring-bind (type length scale nullable) (car (database-query query database :auto nil)) + (values (ensure-keyword type) length scale + (if (char-equal #\Y (schar nullable 0)) 1 0))))) + +;; Return one row of the table referred to by QC, represented as a +;; list; or if there are no more rows, signal an error if EOF-ERRORP, +;; or return EOF-VALUE otherwise. + +;; KLUDGE: This CASE statement is a strong sign that the code would be +;; cleaner if CD were made into an abstract class, we made variant +;; classes for CD-for-column-of-strings, CD-for-column-of-floats, +;; etc., and defined virtual functions to handle operations like +;; get-an-element-from-column. (For a small special purpose module +;; like this, would arguably be overkill, so I'm not going to do it +;; now, but if this code ends up getting more complicated in +;; maintenance, it would become a really good idea.) + +;; Arguably this would be a good place to signal END-OF-FILE, but +;; since the ANSI spec specifically says that END-OF-FILE means a +;; STREAM which has no more data, and QC is not a STREAM, we signal +;; DBI-ERROR instead. + +(uffi:def-type short-array (* :short)) +(uffi:def-type int-array (* :int)) +(uffi:def-type double-array (* :double)) +(uffi:def-type int-pointer (* :int)) +(uffi:def-type double-pointer (* :double)) + +;;; the result of a database query: a cursor through a table +(defstruct (oracle-result-set (:print-function print-query-cursor) + (:conc-name qc-) + (:constructor %make-query-cursor)) + (db (error "missing DB") ; db conn. this table is associated with + :type oracle-database + :read-only t) + (stmthp (error "missing STMTHP") ; the statement handle used to create +;; :type alien ; this table. owned by the QUERY-CURSOR + :read-only t) ; object, deallocated on CLOSE-QUERY + (cds) ; (error "missing CDS") ; column descriptors +; :type (simple-array cd 1) + ; :read-only t) + (n-from-oci + 0 ; buffered rows: number of rows recv'd + :type (integer 0 #.+n-buf-rows+)) ; from the database on the last read + (n-to-dbi + 0 ; number of buffered rows returned, i.e. + :type (integer 0 #.+n-buf-rows+)) ; the index, within the buffered rows, + ; of the next row which hasn't already + ; been returned + (total-n-from-oci + 0 ; total number of bytes recv'd from OCI + :type unsigned-byte) ; in all reads + (oci-end-seen-p nil)) ; Have we seen the end of OCI + ; data, i.e. OCI returning + ; less data than we requested? + ; OCI doesn't seem to like us + ; to try to read more data + ; from it after that.. + + +(defun fetch-row (qc &optional (eof-errorp t) eof-value encoding) + (declare (optimize (speed 3))) + (cond ((zerop (qc-n-from-oci qc)) + (if eof-errorp + (error 'sql-database-error :message + (format nil "no more rows available in ~S" qc)) + eof-value)) + ((>= (qc-n-to-dbi qc) + (qc-n-from-oci qc)) + (refill-qc-buffers qc) + (fetch-row qc nil eof-value encoding)) + (t + (let ((cds (qc-cds qc)) + (reversed-result nil) + (irow (qc-n-to-dbi qc))) + (dotimes (icd (length cds)) + (let* ((cd (aref cds icd)) + (b (foreign-resource-buffer (cd-buffer cd))) + (value + (let* ((arb (foreign-resource-buffer (cd-indicators cd))) + (indicator (uffi:deref-array arb '(:array :short) irow))) + (declare (type short-array arb)) + (unless (= indicator -1) + (ecase (cd-oci-data-type cd) + (#.SQLT-STR + (deref-oci-string b irow (cd-sizeof cd) encoding)) + (#.SQLT-FLT + (locally + (declare (type double-array b)) + (uffi:deref-array b '(:array :double) irow))) + (#.SQLT-INT + (ecase (cd-sizeof cd) + (4 + (locally + (declare (type int-array b)) + (uffi:deref-array b '(:array :int) irow))))) + (#.SQLT-DATE + (deref-oci-string b irow (cd-sizeof cd) encoding))))))) + (when (and (eq :string (cd-result-type cd)) + value + (not (stringp value))) + (setq value (write-to-string value))) + (push value reversed-result))) + (incf (qc-n-to-dbi qc)) + (nreverse reversed-result))))) + +(defun refill-qc-buffers (qc) + (with-slots (errhp) (qc-db qc) + (setf (qc-n-to-dbi qc) 0) + (cond ((qc-oci-end-seen-p qc) + (setf (qc-n-from-oci qc) 0)) + (t + (let ((oci-code (%oci-stmt-fetch + (deref-vp (qc-stmthp qc)) + (deref-vp errhp) + +n-buf-rows+ + +oci-fetch-next+ +oci-default+))) + (ecase oci-code + (#.+oci-success+ (values)) + (#.+oci-no-data+ (setf (qc-oci-end-seen-p qc) t) + (values)) + (#.+oci-error+ (handle-oci-error :database (qc-db qc) + :nulls-ok t)))) + (uffi:with-foreign-object (rowcount 'ub4) + (oci-attr-get (deref-vp (qc-stmthp qc)) + +oci-htype-stmt+ + rowcount + +unsigned-int-null-pointer+ + +oci-attr-row-count+ + (deref-vp errhp)) + (setf (qc-n-from-oci qc) + (- (uffi:deref-pointer rowcount 'ub4) + (qc-total-n-from-oci qc))) + (when (< (qc-n-from-oci qc) +n-buf-rows+) + (setf (qc-oci-end-seen-p qc) t)) + (setf (qc-total-n-from-oci qc) + (uffi:deref-pointer rowcount 'ub4))))) + (values))) + +;; the guts of the SQL function +;; +;; (like the SQL function, but with the QUERY argument hardwired to T, so +;; that the return value is always a cursor instead of a list) + +;; Is this a SELECT statement? SELECT statements are handled +;; specially by OCIStmtExecute(). (Non-SELECT statements absolutely +;; require a nonzero iteration count, while the ordinary choice for a +;; SELECT statement is a zero iteration count. + +;; SELECT statements are the only statements which return tables. We +;; don't free STMTHP in this case, but instead give it to the new +;; QUERY-CURSOR, and the new QUERY-CURSOR becomes responsible for +;; freeing the STMTHP when it is no longer needed. + +(defun sql-stmt-exec (sql-stmt-string db result-types field-names) + (with-slots (envhp svchp errhp) db + (uffi:with-foreign-strings ((c-stmt-string sql-stmt-string)) + (let ((stmthp (uffi:allocate-foreign-object :pointer-void)) + select-p) + + (uffi:with-foreign-object (stmttype :unsigned-short) + (unwind-protect + (progn + (oci-handle-alloc (deref-vp envhp) + stmthp + +oci-htype-stmt+ 0 +null-void-pointer-pointer+) + (oci-stmt-prepare (deref-vp stmthp) + (deref-vp errhp) + c-stmt-string + (uffi:foreign-string-length c-stmt-string) + +oci-ntv-syntax+ +oci-default+ :database db) + (oci-attr-get (deref-vp stmthp) + +oci-htype-stmt+ + stmttype + +unsigned-int-null-pointer+ + +oci-attr-stmt-type+ + (deref-vp errhp) + :database db) + + (setq select-p (= (uffi:deref-pointer stmttype :unsigned-short) 1)) + (let ((iters (if select-p 0 1))) + + (oci-stmt-execute (deref-vp svchp) + (deref-vp stmthp) + (deref-vp errhp) + iters 0 +null-void-pointer+ +null-void-pointer+ +oci-default+ + :database db))) + ;; free resources unless a query + (unless select-p + (oci-handle-free (deref-vp stmthp) +oci-htype-stmt+) + (uffi:free-foreign-object stmthp)))) + + (cond + (select-p + (make-query-cursor db stmthp result-types field-names)) + (t + nil)))))) + + +;; Return a QUERY-CURSOR representing the table returned from the OCI +;; operation done through STMTHP. TYPES is the argument of the same +;; name from the external SQL function, controlling type conversion +;; of the returned arguments. + +(defun make-query-cursor (db stmthp result-types field-names) + (let ((qc (%make-query-cursor :db db + :stmthp stmthp + :cds (make-query-cursor-cds db stmthp + result-types + field-names)))) + (refill-qc-buffers qc) + qc)) + + +;; the hairy part of MAKE-QUERY-CURSOR: Ask OCI for information +;; about table columns, translate the information into a Lisp +;; vector of column descriptors, and return it. + +;; Allegro defines several flavors of type conversion, but this +;; implementation only supports the :AUTO flavor. + +;; A note of explanation: OCI's internal number format uses 21 +;; bytes (42 decimal digits). 2 separate (?) one-byte fields, +;; scale and precision, are used to deduce the nature of these +;; 21 bytes. See pp. 3-10, 3-26, and 6-13 of OCI documentation +;; for more details. + +;; Mac OS X Note: According to table 6-8 in the Oracle 9i OCI +;; documentation, PRECISION may actually be an sb2 instead of a +;; single byte if performing an "implicit describe". Using a +;; signed short instead of an unsigned byte fixes a Mac OS X bug +;; where PRECISION is always zero. -- JJB 20040713 + +;; When calling OCI C code to handle the conversion, we have +;; only two numeric types available to pass the return value: +;; double-float and signed-long. It would be possible to +;; bypass the OCI conversion functions and write Lisp code +;; which reads the 21-byte field directly and decodes +;; it. However this is left as an exercise for the reader. :-) + +;; The following table describes the mapping, based on the implicit +;; assumption that C's "signed long" type is a 32-bit integer. +;; +;; Internal Values SQL Type C Return Type +;; =============== ======== ============= +;; Precision > 0 SCALE = -127 FLOAT --> double-float +;; Precision > 0 && <=9 SCALE = 0 INTEGER --> signed-long +;; Precision = 0 || > 9 SCALE = 0 BIG INTEGER --> double-float +;; Precision > 0 SCALE > 0 DECIMAL --> double-float + +;; (OCI uses 1-based indexing here.) + +;; KLUDGE: This should work for all other data types except those +;; which don't actually fit in their fixed-width field (BLOBs and the +;; like). As Winton says, we (Cadabra) don't need to worry much about +;; those, since we can't reason with them, so we don't use them. But +;; for a more general application it'd be good to have a more +;; selective and rigorously correct test here for whether we can +;; actually handle the given DEREF-DTYPE value. -- WHN 20000106 + +;; Note: The OCI documentation doesn't seem to say whether the COLNAME +;; value returned here is a newly-allocated copy which we're +;; responsible for freeing, or a pointer into some system copy which +;; will be freed when the system itself is shut down. But judging +;; from the way that the result is used in the cdemodsa.c example +;; program, it looks like the latter: we should make our own copy of +;; the value, but not try to free it. + +;; WORKAROUND: OCI seems to return ub2 values for the +;; +oci-attr-data-size+ attribute even though its documentation claims +;; that it returns a ub4, and even though the associated "sizep" value +;; is 4, not 2. In order to make the code here work reliably, without +;; having to patch it later if OCI is ever fixed to match its +;; documentation, we pre-zero COLSIZE before making the call into OCI. + +;; To exercise the weird OCI behavior (thereby blowing up the code +;; below, beware!) try setting this value into COLSIZE, calling OCI, +;; then looking at the value in COLSIZE. (setf colsize #x12345678) +;; debugging only + +;; Mac OS X Note: This workaround fails on a bigendian platform so +;; I've changed the data type of COLNAME to :unsigned-short as per +;; the Oracle 9i OCI documentation. -- JJB 20040713 + +(uffi:def-type byte-pointer (* :byte)) +(uffi:def-type void-pointer-pointer (* :void-pointer)) + +(defun make-query-cursor-cds (database stmthp result-types field-names) + (declare (optimize (safety 3) #+nil (speed 3)) + (type oracle-database database) + (type pointer-pointer-void stmthp)) + (with-slots (errhp) database + (uffi:with-foreign-objects ((dtype-foreign :unsigned-short) + (parmdp :pointer-void) + (precision :short) + (scale :byte) + (colname '(* :unsigned-char)) + (colnamelen 'ub4) + (colsize 'ub2) + (defnp ':pointer-void)) + (let ((buffer nil) + (sizeof nil)) + (do ((icolumn 0 (1+ icolumn)) + (cds-as-reversed-list nil)) + ((not (eql (oci-param-get (deref-vp stmthp) + +oci-htype-stmt+ + (deref-vp errhp) + parmdp + (1+ icolumn) :database database) + +oci-success+)) + (coerce (reverse cds-as-reversed-list) 'simple-vector)) + ;; Decode type of ICOLUMNth column into a type we're prepared to + ;; handle in Lisp. + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + dtype-foreign + +unsigned-int-null-pointer+ + +oci-attr-data-type+ + (deref-vp errhp)) + (let ((dtype (uffi:deref-pointer dtype-foreign :unsigned-short))) + (declare (fixnum dtype)) + (case dtype + (#.SQLT-DATE + (setf buffer (acquire-foreign-resource :unsigned-char + (* 32 +n-buf-rows+))) + (setf sizeof 32 dtype #.SQLT-STR)) + (#.SQLT-NUMBER + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + precision + +unsigned-int-null-pointer+ + +oci-attr-precision+ + (deref-vp errhp)) + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + scale + +unsigned-int-null-pointer+ + +oci-attr-scale+ + (deref-vp errhp)) + (let ((*scale (uffi:deref-pointer scale :byte)) + (*precision (uffi:deref-pointer precision :short))) + + ;;(format t "scale=~d, precision=~d~%" *scale *precision) + (cond + ((or (and (minusp *scale) (zerop *precision)) + (and (zerop *scale) (plusp *precision))) + (setf buffer (acquire-foreign-resource :int +n-buf-rows+) + sizeof 4 ;; sizeof(int) + dtype #.SQLT-INT)) + (t + (setf buffer (acquire-foreign-resource :double +n-buf-rows+) + sizeof 8 ;; sizeof(double) + dtype #.SQLT-FLT))))) + ;; Default to SQL-STR + (t + (setf (uffi:deref-pointer colsize :unsigned-short) 0) + (setf dtype #.SQLT-STR) + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + colsize + +unsigned-int-null-pointer+ + +oci-attr-data-size+ + (deref-vp errhp)) + (let ((colsize-including-null (1+ (uffi:deref-pointer colsize :unsigned-short)))) + (setf buffer (acquire-foreign-resource + :unsigned-char (* +n-buf-rows+ colsize-including-null))) + (setf sizeof colsize-including-null)))) + (let ((retcodes (acquire-foreign-resource :unsigned-short +n-buf-rows+)) + (indicators (acquire-foreign-resource :short +n-buf-rows+)) + (colname-string "")) + (when field-names + (oci-attr-get (deref-vp parmdp) + +oci-dtype-param+ + colname + colnamelen + +oci-attr-name+ + (deref-vp errhp)) + (setq colname-string (uffi:convert-from-foreign-string + (uffi:deref-pointer colname '(* :unsigned-char)) + :length (uffi:deref-pointer colnamelen 'ub4) + :encoding (encoding database)))) + (push (make-cd :name colname-string + :sizeof sizeof + :buffer buffer + :oci-data-type dtype + :retcodes retcodes + :indicators indicators + :result-type (cond + ((consp result-types) + (nth icolumn result-types)) + ((null result-types) + :string) + (t + result-types))) + cds-as-reversed-list) + (oci-define-by-pos (deref-vp stmthp) + defnp + (deref-vp errhp) + (1+ icolumn) ; OCI 1-based indexing again + (foreign-resource-buffer buffer) + sizeof + dtype + (foreign-resource-buffer indicators) + +unsigned-short-null-pointer+ + (foreign-resource-buffer retcodes) + +oci-default+)))))))) + +;; Release the resources associated with a QUERY-CURSOR. + +(defun close-query (qc) + (oci-handle-free (deref-vp (qc-stmthp qc)) +oci-htype-stmt+) + (uffi:free-foreign-object (qc-stmthp qc)) + (let ((cds (qc-cds qc))) + (dotimes (i (length cds)) + (release-cd-resources (aref cds i)))) + (values)) + + +;; Release the resources associated with a column description. + +(defun release-cd-resources (cd) + (free-foreign-resource (cd-buffer cd)) + (free-foreign-resource (cd-retcodes cd)) + (free-foreign-resource (cd-indicators cd)) + (values)) + + +(defmethod database-name-from-spec (connection-spec (database-type (eql :oracle))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (dsn user password) connection-spec + (declare (ignore password)) + (concatenate 'string dsn "/" user))) + + +(defmethod database-connect (connection-spec (database-type (eql :oracle))) + (check-connection-spec connection-spec database-type (dsn user password)) + (destructuring-bind (data-source-name user password) + connection-spec + (let ((envhp (uffi:allocate-foreign-object :pointer-void)) + (errhp (uffi:allocate-foreign-object :pointer-void)) + (svchp (uffi:allocate-foreign-object :pointer-void)) + (srvhp (uffi:allocate-foreign-object :pointer-void))) + ;; Requests to allocate environments and handles should never + ;; fail in normal operation, and they're done too early to + ;; handle errors very gracefully (since they're part of the + ;; error-handling mechanism themselves) so we just assert they + ;; work. + + (setf (deref-vp envhp) +null-void-pointer+) + + #-oci7 + (oci-env-create envhp +oci-default+ +null-void-pointer+ + +null-void-pointer+ +null-void-pointer+ + +null-void-pointer+ 0 +null-void-pointer-pointer+) + + #+oci7 + (progn + (oci-initialize +oci-object+ +null-void-pointer+ +null-void-pointer+ + +null-void-pointer+ +null-void-pointer-pointer+) + (ignore-errors (oci-handle-alloc +null-void-pointer+ envhp + +oci-htype-env+ 0 + +null-void-pointer-pointer+)) ;no testing return + (oci-env-init envhp +oci-default+ 0 +null-void-pointer-pointer+)) + + (oci-handle-alloc (deref-vp envhp) errhp + +oci-htype-error+ 0 +null-void-pointer-pointer+) + (oci-handle-alloc (deref-vp envhp) srvhp + +oci-htype-server+ 0 +null-void-pointer-pointer+) + + (let ((db (make-instance 'oracle-database + :name (database-name-from-spec connection-spec + database-type) + :connection-spec connection-spec + :envhp envhp + :errhp errhp + :database-type :oracle + :svchp svchp + :dsn data-source-name + :user user))) + (uffi:with-foreign-strings ((c-user user) + (c-password password) + (c-data-source-name data-source-name)) + (oci-logon (deref-vp envhp) + (deref-vp errhp) + svchp + c-user (length user) + c-password (length password) + c-data-source-name (length data-source-name) + :database db)) + ;; :date-format-length (1+ (length date-format))))) + (setf (slot-value db 'clsql-sys::state) :open) + (database-execute-command + (format nil "ALTER SESSION SET NLS_DATE_FORMAT='~A'" (date-format db)) db) + (let ((server-version + (caar (database-query + "SELECT BANNER FROM V$VERSION WHERE BANNER LIKE '%Oracle%'" db nil nil)))) + (setf (slot-value db 'server-version) server-version + (slot-value db 'major-server-version) (major-client-version-from-string + server-version))) + db)))) + + +(defun major-client-version-from-string (str) + (cond + ((search " 10g " str) + 10) + ((search "Oracle9i " str) + 9) + ((search "Oracle8" str) + 8))) + +(defun major-server-version-from-string (str) + (when (> (length str) 2) + (cond + ((string= "10." (subseq str 0 3)) + 10) + ((string= "9." (subseq str 0 2)) + 9) + ((string= "8." (subseq str 0 2)) + 8)))) + + +;; Close a database connection. + +(defmethod database-disconnect ((database oracle-database)) + (osucc (oci-logoff (deref-vp (svchp database)) + (deref-vp (errhp database)))) + (osucc (oci-handle-free (deref-vp (envhp database)) +oci-htype-env+)) + ;; Note: It's neither required nor allowed to explicitly deallocate the + ;; ERRHP handle here, since it's owned by the ENVHP deallocated above, + ;; and was therefore automatically deallocated at the same time. + t) + +;;; Do the database operation described in SQL-STMT-STRING on database +;;; DB and, if the command is a SELECT, return a representation of the +;;; resulting table. The representation of the table is controlled by the +;;; QUERY argument: +;;; * If QUERY is NIL, the table is returned as a list of rows, with +;;; each row represented by a list. +;;; * If QUERY is non-NIL, the result is returned as a QUERY-CURSOR +;;; suitable for FETCH-ROW and CLOSE-QUERY +;;; The TYPES argument controls the type conversion method used +;;; to construct the table. The Allegro version supports several possible +;;; values for this argument, but we only support :AUTO. + +(defmethod database-query (query-expression (database oracle-database) result-types field-names) + (let ((cursor (sql-stmt-exec query-expression database result-types field-names))) + ;; (declare (type (or query-cursor null) cursor)) + (if (null cursor) ; No table was returned. + (values) + (do ((reversed-result nil)) + (nil) + (let* ((eof-value :eof) + (row (fetch-row cursor nil eof-value (encoding database)))) + (when (eq row eof-value) + (close-query cursor) + (if field-names + (return (values (nreverse reversed-result) + (loop for cd across (qc-cds cursor) + collect (cd-name cd)))) + (return (nreverse reversed-result)))) + (push row reversed-result)))))) + + +(defmethod database-create-sequence (sequence-name (database oracle-database)) + (execute-command + (concatenate 'string "CREATE SEQUENCE " (sql-escape sequence-name)) + :database database)) + +(defmethod database-drop-sequence (sequence-name (database oracle-database)) + (execute-command + (concatenate 'string "DROP SEQUENCE " (sql-escape sequence-name)) + :database database)) + +(defmethod database-sequence-next (sequence-name (database oracle-database)) + (caar (database-query + (concatenate 'string "SELECT " + (sql-escape sequence-name) + ".NEXTVAL FROM dual") + database :auto nil))) + +(defmethod database-sequence-last (sequence-name (database oracle-database)) + (caar (database-query + (concatenate 'string "SELECT " + (sql-escape sequence-name) + ".CURRVAL FROM dual") + database :auto nil))) + +(defmethod database-set-sequence-position (name position (database oracle-database)) + (without-interrupts + (let* ((next (database-sequence-next name database)) + (incr (- position next))) + (unless (zerop incr) + (database-execute-command + (format nil "ALTER SEQUENCE ~A INCREMENT BY ~D" name incr) + database)) + (database-sequence-next name database) + (database-execute-command + (format nil "ALTER SEQUENCE ~A INCREMENT BY 1" name) + database)))) + +(defmethod database-list-sequences ((database oracle-database) &key owner) + (let ((query + (cond ((null owner) + "select sequence_name from user_sequences") + ((eq owner :all) + "select sequence_name from all_sequences") + (t + (format nil + "select user_sequences.sequence_name from user_sequences,all_sequences where user_sequences.sequence_name=all_sequences.sequence_name and all_sequences.sequence_owner='~:@(~A~)'" + owner))))) + (mapcar #'car (database-query query database nil nil)))) + +(defmethod database-execute-command (sql-expression (database oracle-database)) + (database-query sql-expression database nil nil) + (when (database-autocommit database) + (oracle-commit database)) + t) + + +(defstruct (cd (:constructor make-cd) + (:print-function print-cd)) + "a column descriptor: metadata about the data in a table" + + ;; name of this column + (name (error "missing NAME") :type simple-string :read-only t) + ;; the size in bytes of a single element + (sizeof (error "missing SIZE") :type fixnum :read-only t) + ;; an array of +N-BUF-ROWS+ elements in C representation + (buffer (error "Missing BUFFER") + :type foreign-resource + :read-only t) + ;; an array of +N-BUF-ROWS+ OCI return codes in C representation. + ;; (There must be one return code for every element of every + ;; row in order to be able to represent nullness.) + (retcodes (error "Missing RETCODES") + :type foreign-resource + :read-only t) + (indicators (error "Missing INDICATORS") + :type foreign-resource + :read-only t) + ;; the OCI code for the data type of a single element + (oci-data-type (error "missing OCI-DATA-TYPE") + :type fixnum + :read-only t) + (result-type (error "missing RESULT-TYPE") + :read-only t)) + + +(defun print-cd (cd stream depth) + (declare (ignore depth)) + (print-unreadable-object (cd stream :type t) + (format stream + ":NAME ~S :OCI-DATA-TYPE ~S :OCI-DATA-SIZE ~S" + (cd-name cd) + (cd-oci-data-type cd) + (cd-sizeof cd)))) + +(defun print-query-cursor (qc stream depth) + (declare (ignore depth)) + (print-unreadable-object (qc stream :type t :identity t) + (prin1 (qc-db qc) stream))) + + +(defmethod database-query-result-set ((query-expression string) + (database oracle-database) + &key full-set result-types) + (let ((cursor (sql-stmt-exec query-expression database result-types nil))) + (if full-set + (values cursor (length (qc-cds cursor)) nil) + (values cursor (length (qc-cds cursor)))))) + + +(defmethod database-dump-result-set (result-set (database oracle-database)) + (close-query result-set)) + +(defmethod database-store-next-row (result-set (database oracle-database) list) + (let* ((eof-value :eof) + (row (fetch-row result-set nil eof-value (encoding database)))) + (unless (eq eof-value row) + (loop for i from 0 below (length row) + do (setf (nth i list) (nth i row))) + list))) + +(defmethod database-start-transaction ((database oracle-database)) + (call-next-method) + ;; Not needed with simple transaction + #+ignore + (with-slots (svchp errhp) database + (oci-trans-start (deref-vp svchp) + (deref-vp errhp) + 60 + +oci-trans-new+)) + t) + + +(defun oracle-commit (database) + (with-slots (svchp errhp) database + (osucc (oci-trans-commit (deref-vp svchp) + (deref-vp errhp) + 0)))) + +(defmethod database-commit-transaction ((database oracle-database)) + (call-next-method) + (oracle-commit database) + t) + +(defmethod database-abort-transaction ((database oracle-database)) + (call-next-method) + (osucc (oci-trans-rollback (deref-vp (svchp database)) + (deref-vp (errhp database)) + 0)) + t) + +;; Specifications + +(defmethod db-type-has-bigint? ((type (eql :oracle))) + nil) + +(defmethod db-type-has-fancy-math? ((db-type (eql :oracle))) + t) + +(defmethod db-type-has-boolean-where? ((db-type (eql :oracle))) + nil) + +(when (clsql-sys:database-type-library-loaded :oracle) + (clsql-sys:initialize-database-type :database-type :oracle)) diff --git a/db-postgresql-socket/Makefile b/db-postgresql-socket/Makefile new file mode 100644 index 0000000..31dc910 --- /dev/null +++ b/db-postgresql-socket/Makefile @@ -0,0 +1,6 @@ +SUBDIRS := + +include ../Makefile.common + +.PHONY: distclean +distclean: clean diff --git a/db-postgresql-socket/postgresql-socket-api.lisp b/db-postgresql-socket/postgresql-socket-api.lisp new file mode 100644 index 0000000..40dc86a --- /dev/null +++ b/db-postgresql-socket/postgresql-socket-api.lisp @@ -0,0 +1,973 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-api.lisp +;;;; Purpose: Low-level PostgreSQL interface using sockets +;;;; Authors: Kevin M. Rosenberg based on original code by Pierre R. Mai +;;;; Created: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:postgresql-socket) + +;; KMR: 2011-06-12 +;; FIXME: The file has code specific to sb-unicode and CCL +;; to assume UTF8 encoded strings. +;; Best fix would be to use the user-specified encoding that is now +;; stored in the database object and use the UFFI 2.x encoding functions +;; to convert strings to/from octet vectors. This allows encoding +;; other than UTF8 and also works on all CL implementations that +;; support wide character strings + +(uffi:def-enum pgsql-ftype + ((:bytea 17) + (:int2 21) + (:int4 23) + (:int8 20) + (:float4 700) + (:float8 701))) + +(defmethod clsql-sys:database-type-library-loaded ((database-type + (eql :postgresql-socket))) + "T if foreign library was able to be loaded successfully. Always true for +socket interface" + t) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :postgresql-socket))) + t) + + +;;; Message I/O stuff + +(defmacro define-message-constants (description &rest clauses) + (assert (evenp (length clauses))) + (loop with seen-characters = nil + for (name char) on clauses by #'cddr + for char-code = (char-code char) + for doc-string = (format nil "~A (~:C): ~A" description char name) + if (member char seen-characters) + do (error "Duplicate message type ~@C for group ~A" char description) + else + collect + `(defconstant ,name ,char-code ,doc-string) + into result-clauses + and do (push char seen-characters) + finally + (return `(progn ,@result-clauses)))) + +(eval-when (:compile-toplevel :load-toplevel :execute) +(define-message-constants "Backend Message Constants" + +ascii-row-message+ #\D + +authentication-message+ #\R + +backend-key-message+ #\K + +binary-row-message+ #\B + +completed-response-message+ #\C + +copy-in-response-message+ #\G + +copy-out-response-message+ #\H + +cursor-response-message+ #\P + +empty-query-response-message+ #\I + +error-response-message+ #\E + +function-response-message+ #\V + +notice-response-message+ #\N + +notification-response-message+ #\A + +ready-for-query-message+ #\Z + +row-description-message+ #\T)) + +#+scl +(declaim (inline read-byte write-byte)) + +(defun send-socket-value-int32 (socket value) + (declare (type stream socket) + (type (unsigned-byte 32) value)) + (write-byte (ldb (byte 8 24) value) socket) + (write-byte (ldb (byte 8 16) value) socket) + (write-byte (ldb (byte 8 8) value) socket) + (write-byte (ldb (byte 8 0) value) socket) + nil) + +(defun send-socket-value-int16 (socket value) + (declare (type stream socket) + (type (unsigned-byte 16) value)) + (write-byte (ldb (byte 8 8) value) socket) + (write-byte (ldb (byte 8 0) value) socket) + nil) + +(defun send-socket-value-int8 (socket value) + (declare (type stream socket) + (type (unsigned-byte 8) value)) + (write-byte (ldb (byte 8 0) value) socket) + nil) + +(defun send-socket-value-char-code (socket value) + (declare (type stream socket) + (type character value)) + (write-byte (ldb (byte 8 0) (char-code value)) socket) + nil) + +(defun send-socket-value-string (socket value) + (declare (type stream socket) + (type string value)) + #-(or sb-unicode ccl) + (loop for char across value + for code = (char-code char) + do (write-byte code socket) + finally (write-byte 0 socket)) + #+ccl + (write-sequence (ccl:encode-string-to-octets + value :external-format :utf-8) socket) + #+ccl + (write-byte 0 socket) + #+sb-unicode + (write-sequence (sb-ext:string-to-octets value :null-terminate t) + socket) + nil) + +(defun send-socket-value-limstring (socket value limit) + (declare (type stream socket) + (type string value) + (type fixnum limit)) + (let ((length (length value))) + (dotimes (i (min length limit)) + (let ((code (char-code (char value i)))) + (write-byte code socket))) + (dotimes (i (- limit length)) + (write-byte 0 socket))) + nil) + + +(defun read-socket-value-int32 (socket) + (declare (type stream socket)) + (declare (optimize (speed 3))) + (let ((result 0)) + (declare (type (unsigned-byte 32) result)) + (setf (ldb (byte 8 24) result) (read-byte socket)) + (setf (ldb (byte 8 16) result) (read-byte socket)) + (setf (ldb (byte 8 8) result) (read-byte socket)) + (setf (ldb (byte 8 0) result) (read-byte socket)) + result)) + +(defun read-socket-value-int16 (socket) + (declare (type stream socket)) + (let ((result 0)) + (declare (type (unsigned-byte 16) result)) + (setf (ldb (byte 8 8) result) (read-byte socket)) + (setf (ldb (byte 8 0) result) (read-byte socket)) + result)) + +(defun read-socket-value-int8 (socket) + (declare (type stream socket)) + (read-byte socket)) + + +(defun read-socket-value-string (socket) + (declare (type stream socket)) + #-(or sb-unicode ccl) + (with-output-to-string (out) + (loop for code = (read-byte socket) + until (zerop code) + do (write-char (code-char code) out))) + #+ccl + (let ((bytes (make-array 64 + :element-type '(unsigned-byte 8) + :adjustable t + :fill-pointer 0))) + (loop for code = (read-byte socket) + until (zerop code) + do (vector-push-extend code bytes)) + (ccl:decode-string-from-octets bytes :external-format :utf-8)) + #+sb-unicode + (let ((bytes (make-array 64 + :element-type '(unsigned-byte 8) + :adjustable t + :fill-pointer 0))) + (loop for code = (read-byte socket) + until (zerop code) + do (vector-push-extend code bytes)) + (sb-ext:octets-to-string bytes))) + +(defmacro define-message-sender (name (&rest args) &rest clauses) + (let ((socket-var (gensym)) + (body nil)) + (dolist (clause clauses) + (let* ((type (first clause)) + (fn (intern (concatenate 'string (symbol-name '#:send-socket-value-) + (symbol-name type))))) + (push `(,fn ,socket-var ,@(rest clause)) body))) + `(defun ,name (,socket-var ,@args) + ,@(nreverse body)))) + +(define-message-sender send-startup-message + (database user &optional (command-line "") (backend-tty "")) + (int32 296) ; Length + (int32 #x00020000) ; Version 2.0 + (limstring database 64) + (limstring user 32) + (limstring command-line 64) + (limstring "" 64) ; Unused + (limstring backend-tty 64)) + +(define-message-sender send-terminate-message () + (char-code #\X)) + +(define-message-sender send-unencrypted-password-message (password) + (int32 (+ 5 (length password))) + (string password)) + +(define-message-sender send-query-message (query) + (char-code #\Q) + (string query)) + +(define-message-sender send-encrypted-password-message (crypted-password) + (int32 (+ 5 (length crypted-password))) + (string crypted-password)) + +(define-message-sender send-cancel-request (pid key) + (int32 16) ; Length + (int32 80877102) ; Magic + (int32 pid) + (int32 key)) + +(defun read-bytes (socket length) + "Read a byte array of the given length from a stream." + (declare (type stream socket) + (type fixnum length) + (optimize (speed 3) (safety 0))) + (let ((result (make-array length :element-type '(unsigned-byte 8)))) + (read-sequence result socket) + result)) + +(defun read-socket-sequence (stream length &optional (allow-wide t)) + (declare (stream stream) + (optimize (speed 3) (safety 0))) + #-(or sb-unicode ccl) + (let ((result (make-string length))) + (dotimes (i length result) + (declare (fixnum i)) + (setf (char result i) (code-char (read-byte stream))))) + #+ccl + (let ((bytes (make-array length :element-type '(unsigned-byte 8)))) + (declare (type (simple-array (unsigned-byte 8) (*)) bytes)) + (read-sequence bytes stream) + (if allow-wide + (ccl:decode-string-from-octets bytes :external-format :utf-8) + (map 'string #'code-char bytes))) + #+sb-unicode + (let ((bytes (make-array length :element-type '(unsigned-byte 8)))) + (declare (type (simple-array (unsigned-byte 8) (*)) bytes)) + (read-sequence bytes stream) + (if allow-wide + (sb-ext:octets-to-string bytes) + (map 'string #'code-char bytes)))) + +;;; Support for encrypted password transmission + +#-scl +(eval-when (:compile-toplevel :load-toplevel :execute) + (defvar *crypt-library-loaded* nil) + + (unless *crypt-library-loaded* + (uffi:load-foreign-library + (uffi:find-foreign-library "libcrypt" + '(#+(or 64bit x86-64) "/usr/lib64/" + "/usr/lib/" "/usr/local/lib/" "/lib/")) + :supporting-libraries '("c")) + (setq *crypt-library-loaded* t))) + +(in-package :postgresql-socket) + +(uffi:def-function ("crypt" crypt) + ((key :cstring) + (salt :cstring)) + :returning :cstring) + +(defun crypt-password (password salt) + "Encrypt a password for transmission to a PostgreSQL server." + (uffi:with-cstring (password-cstring password) + (uffi:with-cstring (salt-cstring salt) + (uffi:convert-from-cstring + (crypt password-cstring salt-cstring))))) + + +;;;; Condition hierarchy + +(define-condition postgresql-condition (condition) + ((connection :initarg :connection :reader postgresql-condition-connection) + (message :initarg :message :reader postgresql-condition-message)) + (:report + (lambda (c stream) + (format stream "~@<~A occurred on connection ~A. ~:@_Reason: ~A~:@>" + (type-of c) + (postgresql-condition-connection c) + (postgresql-condition-message c))))) + +(define-condition postgresql-error (error postgresql-condition) + ()) + +(define-condition postgresql-fatal-error (postgresql-error) + ()) + +(define-condition postgresql-login-error (postgresql-fatal-error) + ()) + +(define-condition postgresql-warning (warning postgresql-condition) + ()) + +(define-condition postgresql-notification (postgresql-condition) + () + (:report + (lambda (c stream) + (format stream "~@" + (postgresql-condition-connection c) + (postgresql-condition-message c))))) + +;;; Structures + +(defstruct postgresql-connection + host + port + database + user + password + options + tty + socket + pid + key) + +(defstruct postgresql-cursor + connection + name + fields) + +;;; Socket stuff + +(defconstant +postgresql-server-default-port+ 5432 + "Default port of PostgreSQL server.") + +(defvar *postgresql-server-socket-timeout* 60 + "Timeout in seconds for reads from the PostgreSQL server.") + +#+(or cmu scl) +(defun open-postgresql-socket (host port) + (etypecase host + (pathname + ;; Directory to unix-domain socket + (ext:connect-to-unix-socket + (namestring + (make-pathname :name ".s.PGSQL" :type (princ-to-string port) + :defaults host)))) + (string + (ext:connect-to-inet-socket host port)))) + +#+sbcl +(defun open-postgresql-socket (host port) + (etypecase host + (pathname + ;; Directory to unix-domain socket + (let ((sock (make-instance 'sb-bsd-sockets:local-socket + :type :stream))) + (sb-bsd-sockets:socket-connect + sock + (namestring + (make-pathname :name ".s.PGSQL" :type (princ-to-string port) + :defaults host))) + sock)) + (string + (let ((sock (make-instance 'sb-bsd-sockets:inet-socket + :type :stream + :protocol :tcp))) + (sb-bsd-sockets:socket-connect + sock + (sb-bsd-sockets:host-ent-address + (sb-bsd-sockets:get-host-by-name host)) + port) + sock)))) + +#+(or cmu scl) +(defun open-postgresql-socket-stream (host port) + (system:make-fd-stream + (open-postgresql-socket host port) + :input t :output t :element-type '(unsigned-byte 8) + :buffering :none + :timeout *postgresql-server-socket-timeout*)) + + +#+sbcl +(defun open-postgresql-socket-stream (host port) + (sb-bsd-sockets:socket-make-stream + (open-postgresql-socket host port) :input t :output t + :element-type '(unsigned-byte 8))) + + +#+allegro +(defun open-postgresql-socket-stream (host port) + (etypecase host + (pathname + (let ((path (namestring + (make-pathname :name ".s.PGSQL" :type (princ-to-string port) + :defaults host)))) + (socket:make-socket :type :stream :address-family :file + :connect :active + :remote-filename path :local-filename path))) + (string + (socket:with-pending-connect + (mp:with-timeout (*postgresql-server-socket-timeout* (error "connect failed")) + (socket:make-socket :type :stream :address-family :internet + :remote-port port :remote-host host + :connect :active :nodelay t)))))) + +#+openmcl +(defun open-postgresql-socket-stream (host port) + (etypecase host + (pathname + (let ((path (namestring + (make-pathname :name ".s.PGSQL" :type (princ-to-string port) + :defaults host)))) + (ccl:make-socket :type :stream :address-family :file + :connect :active + :remote-filename path :local-filename path))) + (string + (ccl:make-socket :type :stream :address-family :internet + :remote-port port :remote-host host + :connect :active :nodelay t)))) + +#+lispworks +(defun open-postgresql-socket-stream (host port) + (etypecase host + (pathname + (error "File sockets not supported on Lispworks.")) + (string + (comm:open-tcp-stream host port :direction :io :element-type '(unsigned-byte 8) + :read-timeout *postgresql-server-socket-timeout*)) + )) + + +#+clisp +(defun open-postgresql-socket-stream (host port) + (etypecase host + (pathname + (error "Not supported")) + (string + (socket:socket-connect + port host + :element-type '(unsigned-byte 8) + :timeout *postgresql-server-socket-timeout*)))) + + +;;; Interface Functions + +(defun open-postgresql-connection (&key (host (cmucl-compat:required-argument)) + (port +postgresql-server-default-port+) + (database (cmucl-compat:required-argument)) + (user (cmucl-compat:required-argument)) + options tty password) + "Open a connection to a PostgreSQL server with the given parameters. +Note that host, database and user arguments must be supplied. + +If host is a pathname, it is assumed to name a directory containing +the local unix-domain sockets of the server, with port selecting which +of those sockets to open. If host is a string, it is assumed to be +the name of the host running the PostgreSQL server. In that case a +TCP connection to the given port on that host is opened in order to +communicate with the server. In either case the port argument +defaults to `+postgresql-server-default-port+'. + +Password is the clear-text password to be passed in the authentication +phase to the server. Depending on the server set-up, it is either +passed in the clear, or encrypted via crypt and a server-supplied +salt. In that case the alien function specified by `*crypt-library*' +and `*crypt-function-name*' is used for encryption. + +Note that all the arguments (including the clear-text password +argument) are stored in the `postgresql-connection' structure, in +order to facilitate automatic reconnection in case of communication +troubles." + (reopen-postgresql-connection + (make-postgresql-connection :host host :port port + :options (or options "") :tty (or tty "") + :database database :user user + :password (or password "")))) + +(defun byte-sequence-to-hex-string (sequence) + (string-downcase (format nil "~{~2,'0X~}" (coerce sequence 'list)))) + +(defun encrypt-password-md5 (password user salt) + (let ((pass1 (byte-sequence-to-hex-string + (md5::md5sum-string (concatenate 'string password user))))) + (byte-sequence-to-hex-string + (md5:md5sum-sequence (concatenate '(vector (unsigned-byte 8)) + (map '(vector (unsigned-byte 8)) #'char-code pass1) + salt))))) + +(defun reopen-postgresql-connection (connection) + "Reopen the given PostgreSQL connection. Closes any existing +connection, if it is still open." + (when (postgresql-connection-open-p connection) + (close-postgresql-connection connection)) + (let ((socket (open-postgresql-socket-stream + (postgresql-connection-host connection) + (postgresql-connection-port connection)))) + (unwind-protect + (progn + (setf (postgresql-connection-socket connection) socket) + (send-startup-message socket + (postgresql-connection-database connection) + (postgresql-connection-user connection) + (postgresql-connection-options connection) + (postgresql-connection-tty connection)) + (force-output socket) + (loop + (case (read-socket-value-int8 socket) + (#.+authentication-message+ + (case (read-socket-value-int32 socket) + (0 (return)) + ((1 2) + (error 'postgresql-login-error + :connection connection + :message + "Postmaster expects unsupported Kerberos authentication.")) + (3 + (send-unencrypted-password-message + socket + (postgresql-connection-password connection)) + (force-output socket)) + (4 + (let ((salt (read-socket-sequence socket 2 nil))) + (send-encrypted-password-message + socket + (crypt-password + (postgresql-connection-password connection) salt))) + (force-output socket)) + (5 + (let ((salt (read-bytes socket 4))) + (let ((pwd (encrypt-password-md5 + (postgresql-connection-password connection) + (postgresql-connection-user connection) + salt))) + (send-encrypted-password-message + socket + (concatenate 'string "md5" pwd)))) + (force-output socket)) + (t + (error 'postgresql-login-error + :connection connection + :message + "Postmaster expects unknown authentication method.")))) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (error 'postgresql-login-error + :connection connection :message message))) + (t + (error 'postgresql-login-error + :connection connection + :message + "Received garbled message from Postmaster")))) + ;; Start backend communication + (force-output socket) + (loop + (case (read-socket-value-int8 socket) + (#.+backend-key-message+ + (setf (postgresql-connection-pid connection) + (read-socket-value-int32 socket) + (postgresql-connection-key connection) + (read-socket-value-int32 socket))) + (#.+ready-for-query-message+ + (setq socket nil) + (return connection)) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (error 'postgresql-login-error + :connection connection + :message message))) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (warn 'postgresql-warning :connection connection + :message message))) + (t + (error 'postgresql-login-error + :connection connection + :message + "Received garbled message from Postmaster"))))) + (when socket + (close socket))))) + +(defun close-postgresql-connection (connection &optional abort) + (unless abort + (ignore-errors + (send-terminate-message (postgresql-connection-socket connection)))) + (close (postgresql-connection-socket connection))) + +(defun postgresql-connection-open-p (connection) + (let ((socket (postgresql-connection-socket connection))) + (and socket (streamp socket) (open-stream-p socket)))) + +(defun ensure-open-postgresql-connection (connection) + (unless (postgresql-connection-open-p connection) + (reopen-postgresql-connection connection))) + +(defun process-async-messages (connection) + (assert (postgresql-connection-open-p connection)) + ;; Process any asnychronous messages + (loop with socket = (postgresql-connection-socket connection) + while (listen socket) + do + (case (read-socket-value-int8 socket) + (#.+ready-for-query-message+) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (warn 'postgresql-warning :connection connection + :message message))) + (#.+notification-response-message+ + (let ((pid (read-socket-value-int32 socket)) + (message (read-socket-value-string socket))) + (when (= pid (postgresql-connection-pid connection)) + (signal 'postgresql-notification :connection connection + :message message)))) + (t + (close-postgresql-connection connection) + (error 'postgresql-fatal-error :connection connection + :message "Received garbled message from backend"))))) + +(defun start-query-execution (connection query) + (ensure-open-postgresql-connection connection) + (process-async-messages connection) + (send-query-message (postgresql-connection-socket connection) query) + (force-output (postgresql-connection-socket connection))) + +(defun wait-for-query-results (connection) + (assert (postgresql-connection-open-p connection)) + (let ((socket (postgresql-connection-socket connection)) + (cursor-name nil) + (error nil)) + (loop + (case (read-socket-value-int8 socket) + (#.+completed-response-message+ + (return (values :completed (read-socket-value-string socket)))) + (#.+cursor-response-message+ + (setq cursor-name (read-socket-value-string socket))) + (#.+row-description-message+ + (let* ((count (read-socket-value-int16 socket)) + (fields + (loop repeat count + collect + (list + (read-socket-value-string socket) + (read-socket-value-int32 socket) + (read-socket-value-int16 socket) + (read-socket-value-int32 socket))))) + (return + (values :cursor + (make-postgresql-cursor :connection connection + :name cursor-name + :fields fields))))) + (#.+copy-in-response-message+ + (return :copy-in)) + (#.+copy-out-response-message+ + (return :copy-out)) + (#.+ready-for-query-message+ + (when error + (error error)) + (return nil)) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (setq error + (make-condition 'postgresql-error + :connection connection :message message)))) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (unless (eq :ignore clsql-sys:*backend-warning-behavior*) + (warn 'postgresql-warning + :connection connection :message message)))) + (#.+notification-response-message+ + (let ((pid (read-socket-value-int32 socket)) + (message (read-socket-value-string socket))) + (when (= pid (postgresql-connection-pid connection)) + (signal 'postgresql-notification :connection connection + :message message)))) + (t + (close-postgresql-connection connection) + (error 'postgresql-fatal-error :connection connection + :message "Received garbled message from backend")))))) + +(defun read-null-bit-vector (socket count) + (let ((result (make-array count :element-type 'bit))) + (dotimes (offset (ceiling count 8)) + (loop with byte = (read-byte socket) + for index from (* offset 8) below (min count (* (1+ offset) 8)) + for weight downfrom 7 + do (setf (aref result index) (ldb (byte 1 weight) byte)))) + result)) + + +(defun read-field (socket type) + (let ((length (- (read-socket-value-int32 socket) 4))) + (case type + ((:int32 :int64) + (read-integer-from-socket socket length)) + (:double + (read-double-from-socket socket length)) + (t + (read-socket-sequence socket length))))) + +(uffi:def-constant +char-code-zero+ (char-code #\0)) +(uffi:def-constant +char-code-minus+ (char-code #\-)) +(uffi:def-constant +char-code-plus+ (char-code #\+)) +(uffi:def-constant +char-code-period+ (char-code #\.)) +(uffi:def-constant +char-code-lower-e+ (char-code #\e)) +(uffi:def-constant +char-code-upper-e+ (char-code #\E)) + +(defun read-integer-from-socket (socket length) + (declare (fixnum length)) + (if (zerop length) + nil + (let ((val 0) + (first-char (read-byte socket)) + (minusp nil)) + (declare (fixnum first-char)) + (decf length) ;; read first char + (cond + ((= first-char +char-code-minus+) + (setq minusp t)) + ((= first-char +char-code-plus+) + ) ;; nothing to do + (t + (setq val (- first-char +char-code-zero+)))) + + (dotimes (i length) + (declare (fixnum i)) + (setq val (+ + (* 10 val) + (- (read-byte socket) +char-code-zero+)))) + (if minusp + (- val) + val)))) + +(defmacro ascii-digit (int) + (let ((offset (gensym))) + `(let ((,offset (- ,int +char-code-zero+))) + (declare (fixnum ,int ,offset)) + (if (and (>= ,offset 0) + (< ,offset 10)) + ,offset + nil)))) + +(defun read-double-from-socket (socket length) + (declare (fixnum length)) + (let ((before-decimal 0) + (after-decimal 0) + (decimal-count 0) + (exponent 0) + (decimalp nil) + (minusp nil) + (result nil) + (char (read-byte socket))) + (declare (fixnum char exponent decimal-count)) + (decf length) ;; already read first character + (cond + ((= char +char-code-minus+) + (setq minusp t)) + ((= char +char-code-plus+) + ) + ((= char +char-code-period+) + (setq decimalp t)) + (t + (setq before-decimal (ascii-digit char)) + (unless before-decimal + (error "Unexpected value")))) + + (block loop + (dotimes (i length) + (setq char (read-byte socket)) + ;; (format t "~&len:~D, i:~D, char:~D, minusp:~A, decimalp:~A" length i char minusp decimalp) + (let ((weight (ascii-digit char))) + (cond + ((and weight (not decimalp)) ;; before decimal point + (setq before-decimal (+ weight (* 10 before-decimal)))) + ((and weight decimalp) ;; after decimal point + (setq after-decimal (+ weight (* 10 after-decimal))) + (incf decimal-count)) + ((and (= char +char-code-period+)) + (setq decimalp t)) + ((or (= char +char-code-lower-e+) ;; E is for exponent + (= char +char-code-upper-e+)) + (setq exponent (read-integer-from-socket socket (- length i 1))) + (setq exponent (or exponent 0)) + (return-from loop)) + (t + (break "Unexpected value")) + ) + ))) + (setq result (* (+ (coerce before-decimal 'double-float) + (* after-decimal + (expt 10 (- decimal-count)))) + (expt 10 exponent))) + (if minusp + (- result) + result))) + + +#+ignore +(defun read-double-from-socket (socket length) + (let ((result (make-string length))) + (read-socket-sequence result socket) + (let ((*read-default-float-format* 'double-float)) + (read-from-string result)))) + +(defun read-cursor-row (cursor types) + (let* ((connection (postgresql-cursor-connection cursor)) + (socket (postgresql-connection-socket connection)) + (fields (postgresql-cursor-fields cursor))) + (assert (postgresql-connection-open-p connection)) + (loop + (let ((code (read-socket-value-int8 socket))) + (case code + (#.+ascii-row-message+ + (return + (loop with count = (length fields) + with null-vector = (read-null-bit-vector socket count) + repeat count + for null-bit across null-vector + for i from 0 + for null-p = (zerop null-bit) + if null-p + collect nil + else + collect + (read-field socket (nth i types))))) + (#.+binary-row-message+ + (error "NYI")) + (#.+completed-response-message+ + (return (values nil (read-socket-value-string socket)))) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (error 'postgresql-error + :connection connection :message message))) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (warn 'postgresql-warning + :connection connection :message message))) + (#.+notification-response-message+ + (let ((pid (read-socket-value-int32 socket)) + (message (read-socket-value-string socket))) + (when (= pid (postgresql-connection-pid connection)) + (signal 'postgresql-notification :connection connection + :message message)))) + (t + (close-postgresql-connection connection) + (error 'postgresql-fatal-error :connection connection + :message "Received garbled message from backend"))))))) + +(defun map-into-indexed (result-seq func seq) + (dotimes (i (length seq)) + (declare (fixnum i)) + (setf (elt result-seq i) + (funcall func (elt seq i) i))) + result-seq) + +(defun copy-cursor-row (cursor sequence types) + (let* ((connection (postgresql-cursor-connection cursor)) + (socket (postgresql-connection-socket connection)) + (fields (postgresql-cursor-fields cursor))) + (assert (= (length fields) (length sequence))) + (loop + (let ((code (read-socket-value-int8 socket))) + (case code + (#.+ascii-row-message+ + (return + #+ignore + (let* ((count (length sequence)) + (null-vector (read-null-bit-vector socket count))) + (dotimes (i count) + (declare (fixnum i)) + (if (zerop (elt null-vector i)) + (setf (elt sequence i) nil) + (let ((value (read-field socket (nth i types)))) + (setf (elt sequence i) value))))) + (map-into-indexed + sequence + #'(lambda (null-bit i) + (if (zerop null-bit) + nil + (read-field socket (nth i types)))) + (read-null-bit-vector socket (length sequence))))) + (#.+binary-row-message+ + (error "NYI")) + (#.+completed-response-message+ + (return (values nil (read-socket-value-string socket)))) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (error 'postgresql-error + :connection connection :message message))) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (warn 'postgresql-warning + :connection connection :message message))) + (#.+notification-response-message+ + (let ((pid (read-socket-value-int32 socket)) + (message (read-socket-value-string socket))) + (when (= pid (postgresql-connection-pid connection)) + (signal 'postgresql-notification :connection connection + :message message)))) + (t + (close-postgresql-connection connection) + (error 'postgresql-fatal-error :connection connection + :message "Received garbled message from backend"))))))) + +(defun skip-cursor-row (cursor) + (let* ((connection (postgresql-cursor-connection cursor)) + (socket (postgresql-connection-socket connection)) + (fields (postgresql-cursor-fields cursor))) + (loop + (let ((code (read-socket-value-int8 socket))) + (case code + (#.+ascii-row-message+ + (loop for null-bit across + (read-null-bit-vector socket (length fields)) + do + (unless (zerop null-bit) + (let* ((length (read-socket-value-int32 socket))) + (loop repeat (- length 4) do (read-byte socket))))) + (return t)) + (#.+binary-row-message+ + (error "NYI")) + (#.+completed-response-message+ + (return (values nil (read-socket-value-string socket)))) + (#.+error-response-message+ + (let ((message (read-socket-value-string socket))) + (error 'postgresql-error + :connection connection :message message))) + (#.+notice-response-message+ + (let ((message (read-socket-value-string socket))) + (warn 'postgresql-warning + :connection connection :message message))) + (#.+notification-response-message+ + (let ((pid (read-socket-value-int32 socket)) + (message (read-socket-value-string socket))) + (when (= pid (postgresql-connection-pid connection)) + (signal 'postgresql-notification :connection connection + :message message)))) + (t + (close-postgresql-connection connection) + (error 'postgresql-fatal-error :connection connection + :message "Received garbled message from backend"))))))) + +(defun run-query (connection query &optional (result-types nil)) + (start-query-execution connection query) + (multiple-value-bind (status cursor) + (wait-for-query-results connection) + (assert (eq status :cursor)) + (loop for row = (read-cursor-row cursor result-types) + while row + collect row + finally + (wait-for-query-results connection)))) + +#+scl +(declaim (ext:maybe-inline read-byte write-byte)) diff --git a/db-postgresql-socket/postgresql-socket-package.lisp b/db-postgresql-socket/postgresql-socket-package.lisp new file mode 100644 index 0000000..fbedb51 --- /dev/null +++ b/db-postgresql-socket/postgresql-socket-package.lisp @@ -0,0 +1,58 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-package.lisp +;;;; Purpose: Package definition for PostgreSQL interface using sockets +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +#+lispworks (require "comm") + +(defpackage #:postgresql-socket + (:use #:cl #:md5) + (:export #:pgsql-ftype + #:pgsql-ftype#bytea + #:pgsql-ftype#int2 + #:pgsql-ftype#int4 + #:pgsql-ftype#int8 + #:pgsql-ftype#float4 + #:pgsql-ftype#float8 + + #:+crypt-library+ + #:postgresql-condition + #:postgresql-condition-connection + #:postgresql-condition-message + #:postgresql-error + #:postgresql-fatal-error + #:postgresql-login-error + #:postgresql-warning + #:postgresql-notification + #:postgresql-connection + #:postgresql-connection-p + #:postgresql-cursor + #:postgresql-cursor-p + #:postgresql-cursor-connection + #:postgresql-cursor-name + #:postgresql-cursor-fields + #:+postgresql-server-default-port+ + #:open-postgresql-connection + #:reopen-postgresql-connection + #:close-postgresql-connection + #:postgresql-connection-open-p + #:ensure-open-postgresql-connection + #:start-query-execution + #:wait-for-query-results + #:read-cursor-row + #:copy-cursor-row + #:skip-cursor-row + )) diff --git a/db-postgresql-socket/postgresql-socket-sql.lisp b/db-postgresql-socket/postgresql-socket-sql.lisp new file mode 100644 index 0000000..352198a --- /dev/null +++ b/db-postgresql-socket/postgresql-socket-sql.lisp @@ -0,0 +1,346 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-sql.sql +;;;; Purpose: High-level PostgreSQL interface using socket +;;;; Authors: Kevin M. Rosenberg based on original code by Pierre R. Mai +;;;; Created: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage :clsql-postgresql-socket + (:use #:common-lisp #:clsql-sys #:postgresql-socket) + (:export #:postgresql-socket-database) + (:documentation "This is the CLSQL socket interface to PostgreSQL.")) + +(in-package #:clsql-postgresql-socket) + +;; interface foreign library loading routines + + +(clsql-sys:database-type-load-foreign :postgresql-socket) + + +;; Field type conversion + +(defun make-type-list-for-auto (cursor) + (let* ((fields (postgresql-cursor-fields cursor)) + (num-fields (length fields)) + (new-types '())) + (dotimes (i num-fields) + (declare (fixnum i)) + (push (canonical-field-type fields i) new-types)) + (nreverse new-types))) + +(defun canonical-field-type (fields index) + "Extracts canonical field type from fields list" + (let ((oid (cadr (nth index fields)))) + (case oid + ((#.pgsql-ftype#bytea + #.pgsql-ftype#int2 + #.pgsql-ftype#int4) + :int32) + (#.pgsql-ftype#int8 + :int64) + ((#.pgsql-ftype#float4 + #.pgsql-ftype#float8) + :double) + (otherwise + t)))) + +(defun canonicalize-types (types cursor) + (if (null types) + nil + (let ((auto-list (make-type-list-for-auto cursor))) + (cond + ((listp types) + (canonicalize-type-list types auto-list)) + ((eq types :auto) + auto-list) + (t + nil))))) + +(defun canonicalize-type-list (types auto-list) + "Ensure a field type list meets expectations. Essentially if we get a + generic term for a type that our auto typer pulls a better type for, + use it instead" + (let ((length-types (length types))) + (loop for i from 0 below (length auto-list) + for auto = (nth i auto-list) + collect + (if (or (>= i length-types) + (member (nth i types) (list T :int :double))) + auto + (nth i types))))) + + +(defun convert-to-clsql-warning (database condition) + (ecase *backend-warning-behavior* + (:warn + (warn 'sql-database-warning :database database + :message (postgresql-condition-message condition))) + (:error + (error 'sql-database-error :database database + :message (format nil "Warning upgraded to error: ~A" + (postgresql-condition-message condition)))) + ((:ignore nil) + ;; do nothing + ))) + +(defun convert-to-clsql-error (database expression condition) + (error 'sql-database-data-error + :database database + :expression expression + :error-id (type-of condition) + :message (postgresql-condition-message condition))) + +(defmacro with-postgresql-handlers + ((database &optional expression) + &body body) + (let ((database-var (gensym)) + (expression-var (gensym))) + `(let ((,database-var ,database) + (,expression-var ,expression)) + (handler-bind ((postgresql-warning + (lambda (c) + (convert-to-clsql-warning ,database-var c))) + (postgresql-error + (lambda (c) + (convert-to-clsql-error + ,database-var ,expression-var c)))) + ,@body)))) + +(defmethod database-initialize-database-type ((database-type + (eql :postgresql-socket))) + t) + +(defclass postgresql-socket-database (generic-postgresql-database) + ((connection :accessor database-connection :initarg :connection + :type postgresql-connection))) + +(defmethod database-type ((database postgresql-socket-database)) + :postgresql-socket) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :postgresql-socket))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional port options tty) + connection-spec + (declare (ignore password options tty)) + (concatenate 'string + (etypecase host + (null + "localhost") + (pathname (namestring host)) + (string host)) + (when port + (concatenate 'string + ":" + (etypecase port + (integer (write-to-string port)) + (string port)))) + "/" db "/" user))) + +(defmethod database-connect (connection-spec + (database-type (eql :postgresql-socket))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional + (port +postgresql-server-default-port+) + (options "") (tty "")) + connection-spec + (handler-case + (handler-bind ((postgresql-warning + (lambda (c) + (warn 'sql-warning + :format-control "~A" + :format-arguments + (list (princ-to-string c)))))) + (open-postgresql-connection :host host :port port + :options options :tty tty + :database db :user user + :password password)) + (postgresql-error (c) + ;; Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (type-of c) + :message (postgresql-condition-message c))) + (:no-error (connection) + ;; Success, make instance + (make-instance 'postgresql-socket-database + :name (database-name-from-spec connection-spec + database-type) + :database-type :postgresql-socket + :connection-spec connection-spec + :connection connection))))) + +(defmethod database-disconnect ((database postgresql-socket-database)) + (close-postgresql-connection (database-connection database)) + t) + +(defmethod database-query (expression (database postgresql-socket-database) result-types field-names) + (let ((connection (database-connection database))) + (with-postgresql-handlers (database expression) + (start-query-execution connection expression) + (multiple-value-bind (status cursor) + (wait-for-query-results connection) + (unless (eq status :cursor) + (close-postgresql-connection connection) + (error 'sql-database-data-error + :database database + :expression expression + :error-id "missing-result" + :message "Didn't receive result cursor for query.")) + (setq result-types (canonicalize-types result-types cursor)) + (values + (loop for row = (read-cursor-row cursor result-types) + while row + collect row + finally + (unless (null (wait-for-query-results connection)) + (close-postgresql-connection connection) + (error 'sql-database-data-error + :database database + :expression expression + :error-id "multiple-results" + :message "Received multiple results for query."))) + (when field-names + (mapcar #'car (postgresql-cursor-fields cursor)))))))) + +(defmethod database-execute-command + (expression (database postgresql-socket-database)) + (let ((connection (database-connection database))) + (with-postgresql-handlers (database expression) + (start-query-execution connection expression) + (multiple-value-bind (status result) + (wait-for-query-results connection) + (when (eq status :cursor) + (loop + (multiple-value-bind (row stuff) + (skip-cursor-row result) + (unless row + (setq status :completed result stuff) + (return))))) + (cond + ((null status) + t) + ((eq status :completed) + (unless (null (wait-for-query-results connection)) + (close-postgresql-connection connection) + (error 'sql-database-data-error + :database database + :expression expression + :error-id "multiple-results" + :message "Received multiple results for command.")) + result) + (t + (close-postgresql-connection connection) + (error 'sql-database-data-error + :database database + :expression expression + :errno "missing-result" + :message "Didn't receive completion for command."))))))) + +(defstruct postgresql-socket-result-set + (done nil) + (cursor nil) + (types nil)) + +(defmethod database-query-result-set ((expression string) + (database postgresql-socket-database) + &key full-set result-types) + (declare (ignore full-set)) + (let ((connection (database-connection database))) + (with-postgresql-handlers (database expression) + (start-query-execution connection expression) + (multiple-value-bind (status cursor) + (wait-for-query-results connection) + (unless (eq status :cursor) + (close-postgresql-connection connection) + (error 'sql-database-data-error + :database database + :expression expression + :error-id "missing-result" + :message "Didn't receive result cursor for query.")) + (values (make-postgresql-socket-result-set + :done nil + :cursor cursor + :types (canonicalize-types result-types cursor)) + (length (postgresql-cursor-fields cursor))))))) + +(defmethod database-dump-result-set (result-set + (database postgresql-socket-database)) + (if (postgresql-socket-result-set-done result-set) + t + (with-postgresql-handlers (database) + (loop while (skip-cursor-row + (postgresql-socket-result-set-cursor result-set)) + finally (setf (postgresql-socket-result-set-done result-set) t))))) + +(defmethod database-store-next-row (result-set + (database postgresql-socket-database) + list) + (let ((cursor (postgresql-socket-result-set-cursor result-set))) + (with-postgresql-handlers (database) + (if (copy-cursor-row cursor + list + (postgresql-socket-result-set-types + result-set)) + t + (prog1 nil + (setf (postgresql-socket-result-set-done result-set) t) + (wait-for-query-results (database-connection database))))))) + +(defmethod database-create (connection-spec (type (eql :postgresql-socket))) + (destructuring-bind (host name user password &optional port options tty) connection-spec + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "create database ~A" name) database) + (database-disconnect database))))) + +(defmethod database-destroy (connection-spec (type (eql :postgresql-socket))) + (destructuring-bind (host name user password &optional port optional tty) connection-spec + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "drop database ~A" name) database) + (database-disconnect database))))) + + +(defmethod database-probe (connection-spec (type (eql :postgresql-socket))) + (when (find (second connection-spec) (database-list connection-spec type) + :test #'string-equal) + t)) + + +;; Database capabilities + +(defmethod db-backend-has-create/destroy-db? ((db-type (eql :postgresql-socket))) + nil) + +(defmethod db-type-has-fancy-math? ((db-type (eql :postgresql-socket))) + t) + +(defmethod db-type-default-case ((db-type (eql :postgresql-socket))) + :lower) + +(defmethod database-underlying-type ((database postgresql-socket-database)) + :postgresql) + +(when (clsql-sys:database-type-library-loaded :postgresql-socket) + (clsql-sys:initialize-database-type :database-type :postgresql-socket)) diff --git a/db-postgresql-socket3/api.lisp b/db-postgresql-socket3/api.lisp new file mode 100644 index 0000000..ad6ca18 --- /dev/null +++ b/db-postgresql-socket3/api.lisp @@ -0,0 +1,64 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-api.lisp +;;;; Purpose: Low-level PostgreSQL interface using sockets +;;;; Authors: Kevin M. Rosenberg based on original code by Pierre R. Mai +;;;; Created: Feb 2002 +;;;; +;;;; $Id$ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2004 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:postgresql-socket3) + +(defmethod clsql-sys:database-type-load-foreign ((database-type (eql :postgresql-socket3))) + t) + +(defmethod clsql-sys:database-type-library-loaded ((database-type + (eql :postgresql-socket3))) + "T if foreign library was able to be loaded successfully. Always true for +socket interface" + t) + +(defparameter +postgresql-server-default-port+ 5432 + "Default port of PostgreSQL server.") + +;;;; Condition hierarchy + +(define-condition postgresql-condition (condition) + ((connection :initarg :connection :reader postgresql-condition-connection) + (message :initarg :message :reader postgresql-condition-message)) + (:report + (lambda (c stream) + (format stream "~@<~A occurred on connection ~A. ~:@_Reason: ~A~:@>" + (type-of c) + (postgresql-condition-connection c) + (postgresql-condition-message c))))) + +(define-condition postgresql-error (error postgresql-condition) + ()) + +(define-condition postgresql-fatal-error (postgresql-error) + ()) + +(define-condition postgresql-login-error (postgresql-fatal-error) + ()) + +(define-condition postgresql-warning (warning postgresql-condition) + ()) + +(define-condition postgresql-notification (postgresql-condition) + () + (:report + (lambda (c stream) + (format stream "~@" + (postgresql-condition-connection c) + (postgresql-condition-message c))))) \ No newline at end of file diff --git a/db-postgresql-socket3/package.lisp b/db-postgresql-socket3/package.lisp new file mode 100644 index 0000000..3d13e4c --- /dev/null +++ b/db-postgresql-socket3/package.lisp @@ -0,0 +1,35 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-package.lisp +;;;; Purpose: Package definition for PostgreSQL interface using sockets +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; $Id$ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +#+lispworks (require "comm") + +(defpackage #:postgresql-socket3 + (:use #:cl md5 #:cl-postgres) + (:shadow #:postgresql-warning #:postgresql-notification) + (:export #:+postgresql-server-default-port+ + #:postgresql-condition + #:postgresql-error + #:postgresql-fatal-error + #:postgresql-login-error + #:postgresql-warning + #:postgresql-notification + #:postgresql-condition-message + #:postgresql-condition-connection)) + diff --git a/db-postgresql-socket3/sql.lisp b/db-postgresql-socket3/sql.lisp new file mode 100644 index 0000000..3172e6d --- /dev/null +++ b/db-postgresql-socket3/sql.lisp @@ -0,0 +1,328 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-sql.sql +;;;; Purpose: High-level PostgreSQL interface using socket +;;;; Authors: Kevin M. Rosenberg based on original code by Pierre R. Mai +;;;; Created: Feb 2002 +;;;; +;;;; $Id$ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2007 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage :clsql-postgresql-socket3 + (:use #:common-lisp #:clsql-sys #:postgresql-socket3) + (:export #:postgresql-socket3-database) + (:documentation "This is the CLSQL socket interface (protocol version 3) to PostgreSQL.")) + +(in-package #:clsql-postgresql-socket3) + +(defvar *sqlreader* (cl-postgres:copy-sql-readtable)) +(let ((dt-fn (lambda (useconds-since-2000) + (let ((sec (truncate + (/ useconds-since-2000 + 1000000))) + (usec (mod useconds-since-2000 + 1000000))) + (clsql:make-time :year 2000 :second sec :usec usec))))) + (cl-postgres:set-sql-datetime-readers + :table *sqlreader* + :date (lambda (days-since-2000) + (clsql:make-date :year 2000 :day (+ 1 days-since-2000))) + :timestamp dt-fn + :timestamp-with-timezone dt-fn)) + + + +;; interface foreign library loading routines + +(clsql-sys:database-type-load-foreign :postgresql-socket3) + + +(defmethod database-initialize-database-type ((database-type + (eql :postgresql-socket3))) + t) + + +;; Field type conversion +(defun convert-to-clsql-warning (database condition) + (ecase *backend-warning-behavior* + (:warn + (warn 'sql-database-warning :database database + :message (cl-postgres:database-error-message condition))) + (:error + (error 'sql-database-error :database database + :message (format nil "Warning upgraded to error: ~A" + (cl-postgres:database-error-message condition)))) + ((:ignore nil) + ;; do nothing + ))) + +(defun convert-to-clsql-error (database expression condition) + (error 'sql-database-data-error + :database database + :expression expression + :error-id (type-of condition) + :message (cl-postgres:database-error-message condition))) + +(defmacro with-postgresql-handlers + ((database &optional expression) + &body body) + (let ((database-var (gensym)) + (expression-var (gensym))) + `(let ((,database-var ,database) + (,expression-var ,expression)) + (handler-bind ((postgresql-warning + (lambda (c) + (convert-to-clsql-warning ,database-var c))) + (cl-postgres:database-error + (lambda (c) + (convert-to-clsql-error + ,database-var ,expression-var c)))) + ,@body)))) + + + +(defclass postgresql-socket3-database (generic-postgresql-database) + ((connection :accessor database-connection :initarg :connection + :type cl-postgres:database-connection))) + +(defmethod database-type ((database postgresql-socket3-database)) + :postgresql-socket3) + +(defmethod database-name-from-spec (connection-spec (database-type (eql :postgresql-socket3))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional port options tty) + connection-spec + (declare (ignore password options tty)) + (concatenate 'string + (etypecase host + (null + "localhost") + (keyword "unix") + (pathname (namestring host)) + (string host)) + (when port + (concatenate 'string + ":" + (etypecase port + (integer (write-to-string port)) + (string port)))) + "/" db "/" user))) + +(defmethod database-connect (connection-spec + (database-type (eql :postgresql-socket3))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional + (port +postgresql-server-default-port+) + (options "") (tty "")) + connection-spec + (declare (ignore options tty)) + (handler-case + (handler-bind ((warning + (lambda (c) + (warn 'sql-warning + :format-control "~A" + :format-arguments + (list (princ-to-string c)))))) + (cl-postgres:open-database db user password host port)) + (cl-postgres:database-error (c) + ;; Connect failed + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (type-of c) + :message (cl-postgres:database-error-message c))) + (:no-error (connection) + ;; Success, make instance + (make-instance 'postgresql-socket3-database + :name (database-name-from-spec connection-spec database-type) + :database-type :postgresql-socket3 + :connection-spec connection-spec + :connection connection))))) + +(defmethod database-disconnect ((database postgresql-socket3-database)) + (cl-postgres:close-database (database-connection database)) + t) + +(defvar *include-field-names* nil) + + +;; THE FOLLOWING MACRO EXPANDS TO THE FUNCTION BELOW IT, +;; BUT TO GET null CONVENTIONS CORRECT I NEEDED TO TWEAK THE EXPANSION +;; +;; (cl-postgres:def-row-reader clsql-default-row-reader (fields) +;; (values (loop :while (cl-postgres:next-row) +;; :collect (loop :for field :across fields +;; :collect (cl-postgres:next-field field))) +;; (when *include-field-names* +;; (loop :for field :across fields +;; :collect (cl-postgres:field-name field))))) + + + +(defun clsql-default-row-reader (stream fields) + (declare (type stream stream) + (type (simple-array cl-postgres::field-description) fields)) + (flet ((cl-postgres:next-row () + (cl-postgres::look-for-row stream)) + (cl-postgres:next-field (cl-postgres::field) + (declare (type cl-postgres::field-description cl-postgres::field)) + (let ((cl-postgres::size (cl-postgres::read-int4 stream))) + (declare (type (signed-byte 32) cl-postgres::size)) + (if (eq cl-postgres::size -1) + nil + (funcall (cl-postgres::field-interpreter cl-postgres::field) + stream cl-postgres::size))))) + (let ((results (loop :while (cl-postgres:next-row) + :collect (loop :for field :across fields + :collect (cl-postgres:next-field field)))) + (col-names (when *include-field-names* + (loop :for field :across fields + :collect (cl-postgres:field-name field))))) + ;;multiple return values were not working here + (list results col-names)))) + +(defmethod database-query ((expression string) (database postgresql-socket3-database) result-types field-names) + (let ((connection (database-connection database)) + (cl-postgres:*sql-readtable* *sqlreader*)) + (with-postgresql-handlers (database expression) + (let ((*include-field-names* field-names)) + (apply #'values (cl-postgres:exec-query connection expression #'clsql-default-row-reader))) + ))) + +(defmethod query ((obj command-object) &key (database *default-database*) + (result-types :auto) (flatp nil) (field-names t)) + (clsql-sys::record-sql-command + (format nil "~&~A~&{Params: ~{~A~^, ~}}" + (expression obj) + (parameters obj)) + database) + (multiple-value-bind (rows names) + (database-query obj database result-types field-names) + (let ((result (if (and flatp (= 1 (length (car rows)))) + (mapcar #'car rows) + rows))) + (clsql-sys::record-sql-result result database) + (if field-names + (values result names) + result)))) + +(defmethod database-query ((obj command-object) (database postgresql-socket3-database) result-types field-names) + (let ((connection (database-connection database)) + (cl-postgres:*sql-readtable* *sqlreader*)) + (with-postgresql-handlers (database obj) + (let ((*include-field-names* field-names)) + (unless (has-been-prepared obj) + (cl-postgres:prepare-query connection (prepared-name obj) (expression obj)) + (setf (has-been-prepared obj) T)) + (apply #'values (cl-postgres:exec-prepared + connection + (prepared-name obj) + (parameters obj) + #'clsql-default-row-reader)))))) + +(defmethod database-execute-command + ((expression string) (database postgresql-socket3-database)) + (let ((connection (database-connection database))) + (with-postgresql-handlers (database expression) + ;; return row count? + (second (multiple-value-list (cl-postgres:exec-query connection expression)))))) + +(defmethod execute-command ((obj command-object) + &key (database *default-database*)) + (clsql-sys::record-sql-command (expression obj) database) + (let ((res (database-execute-command obj database))) + (clsql-sys::record-sql-result res database) + ;; return row count? + res)) + +(defmethod database-execute-command + ((obj command-object) (database postgresql-socket3-database)) + (let ((connection (database-connection database))) + (with-postgresql-handlers (database obj) + (unless (has-been-prepared obj) + (cl-postgres:prepare-query connection (prepared-name obj) (expression obj)) + (setf (has-been-prepared obj) T)) + (second (multiple-value-list (cl-postgres:exec-prepared connection (prepared-name obj) (parameters obj))))))) + +;;;; Cursoring interface + + +(defmethod database-query-result-set ((expression string) + (database postgresql-socket3-database) + &key full-set result-types) + (declare (ignore result-types)) + (declare (ignore full-set)) + (error "Cursoring interface is not supported for postgresql-socket3-database try cl-postgres:exec-query with a custom row-reader")) + +(defmethod database-dump-result-set (result-set + (database postgresql-socket3-database)) + (error "Cursoring interface is not supported for postgresql-socket3-database try cl-postgres:exec-query with a custom row-reader") + T) + +(defmethod database-store-next-row (result-set + (database postgresql-socket3-database) + list) + (error "Cursoring interface is not supported for postgresql-socket3-database try cl-postgres:exec-query with a custom row-reader")) + + +;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defmethod database-create (connection-spec (type (eql :postgresql-socket3))) + (destructuring-bind (host name user password &optional port options tty) connection-spec + (declare (ignore port options tty)) + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "create database ~A" name) database) + (database-disconnect database))))) + +(defmethod database-destroy (connection-spec (type (eql :postgresql-socket3))) + (destructuring-bind (host name user password &optional port options tty) connection-spec + (declare (ignore port options tty)) + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "drop database ~A" name) database) + (database-disconnect database))))) + + +(defmethod database-probe (connection-spec (type (eql :postgresql-socket3))) + (when (find (second connection-spec) (database-list connection-spec type) + :test #'string-equal) + t)) + + +;; Database capabilities + +(defmethod db-backend-has-create/destroy-db? ((db-type (eql :postgresql-socket3))) + nil) + +(defmethod db-type-has-fancy-math? ((db-type (eql :postgresql-socket3))) + t) + +(defmethod db-type-default-case ((db-type (eql :postgresql-socket3))) + :lower) + +(defmethod database-underlying-type ((database postgresql-socket3-database)) + :postgresql) + +(when (clsql-sys:database-type-library-loaded :postgresql-socket3) + (clsql-sys:initialize-database-type :database-type :postgresql-socket3)) + + diff --git a/db-postgresql/Makefile b/db-postgresql/Makefile new file mode 100644 index 0000000..31dc910 --- /dev/null +++ b/db-postgresql/Makefile @@ -0,0 +1,6 @@ +SUBDIRS := + +include ../Makefile.common + +.PHONY: distclean +distclean: clean diff --git a/db-postgresql/postgresql-api.lisp b/db-postgresql/postgresql-api.lisp new file mode 100644 index 0000000..c0b8a00 --- /dev/null +++ b/db-postgresql/postgresql-api.lisp @@ -0,0 +1,302 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql.cl +;;;; Purpose: Low-level PostgreSQL interface using UFFI +;;;; Programmers: Kevin M. Rosenberg based on +;;;; Original code by Pierre R. Mai +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:pgsql) + + +;;;; This file implements as little of the FFI bindings to the +;;;; PostgreSQL client libraries as we could get away with. +;;;; Especially all the PostgreSQL-specific goodies aren't there, and +;;;; we just use void pointers where we can get away with it, which +;;;; thanks to the design of the PostgreSQL client libraries is pretty +;;;; much everywhere, in contrast to the MySQL client libraries for +;;;; example. + +;;;; Type definitions + +;;; Basic Types + +(uffi:def-foreign-type pgsql-oid :unsigned-int) + +(uffi:def-enum pgsql-conn-status-type + (:connection-ok + :connection-bad)) + +(uffi:def-enum pgsql-exec-status-type + (:empty-query + :command-ok + :tuples-ok + :copy-out + :copy-in + :bad-response + :nonfatal-error + :fatal-error)) + +(uffi:def-foreign-type pgsql-conn :pointer-void) +(uffi:def-foreign-type pgsql-result :pointer-void) + +(uffi:def-type pgsql-conn-ptr :pointer-void) + +(uffi:def-enum pgsql-ftype + ((:bytea 17) + (:int2 21) + (:int4 23) + (:int8 20) + (:float4 700) + (:float8 701))) + +;;(declaim (inline PQsetdbLogin)) ;; causes compile error in LW 4.2.0 +(uffi:def-function ("PQsetdbLogin" PQsetdbLogin) + ((pghost :cstring) + (pgport :cstring) + (pgoptions :cstring) + (pgtty :cstring) + (dbName :cstring) + (login :cstring) + (pwd :cstring)) + :module "postgresql" + :returning pgsql-conn) + +(declaim (inline PQfinish)) +(uffi:def-function ("PQfinish" PQfinish) + ((conn pgsql-conn)) + :module "postgresql" + :returning :void) + +(declaim (inline PQstatus)) +(uffi:def-function ("PQstatus" PQstatus) + ((conn pgsql-conn)) + :module "postgresql" + :returning pgsql-conn-status-type) + +(declaim (inline PQerrorMessage)) +(uffi:def-function ("PQerrorMessage" PQerrorMessage) + ((conn pgsql-conn)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQexec)) +(uffi:def-function ("PQexec" PQexec) + ((conn pgsql-conn) + (query :cstring)) + :module "postgresql" + :returning pgsql-result) + +(declaim (inline PQresultStatus)) +(uffi:def-function ("PQresultStatus" PQresultStatus) + ((res pgsql-result)) + :module "postgresql" + :returning pgsql-exec-status-type) + +; From postgres_ext.h + +; #define PG_DIAG_SEVERITY 'S' +; #define PG_DIAG_SQLSTATE 'C' +; #define PG_DIAG_MESSAGE_PRIMARY 'M' +; #define PG_DIAG_MESSAGE_DETAIL 'D' +; #define PG_DIAG_MESSAGE_HINT 'H' +; #define PG_DIAG_STATEMENT_POSITION 'P' +; #define PG_DIAG_INTERNAL_POSITION 'p' +; #define PG_DIAG_INTERNAL_QUERY 'q' +; #define PG_DIAG_CONTEXT 'W' +; #define PG_DIAG_SOURCE_FILE 'F' +; #define PG_DIAG_SOURCE_LINE 'L' +; #define PG_DIAG_SOURCE_FUNCTION 'R' +(defconstant +PG-DIAG-SEVERITY+ (char-code #\S)) +(defconstant +PG-DIAG-SQLSTATE+ (char-code #\C)) +(defconstant +PG-DIAG-MESSAGE-PRIMARY+ (char-code #\M)) +(defconstant +PG-DIAG-MESSAGE-DETAIL+ (char-code #\D)) +(defconstant +PG-DIAG-MESSAGE-HINT+ (char-code #\H)) +(defconstant +PG-DIAG-STATEMENT-POSITION+ (char-code #\P)) +(defconstant +PG-DIAG-INTERNAL-POSITION+ (char-code #\p)) +(defconstant +PG-DIAG-INTERNAL-QUERY+ (char-code #\q)) +(defconstant +PG-DIAG-CONTEXT+ (char-code #\W)) +(defconstant +PG-DIAG-SOURCE-FILE+ (char-code #\F)) +(defconstant +PG-DIAG-SOURCE-LINE+ (char-code #\L)) +(defconstant +PG-DIAG-SOURCE-FUNCTION+ (char-code #\R)) + +; PQresultErrorField can return diagnostic information about an error +(declaim (inline PQresultErrorField)) +(uffi:def-function ("PQresultErrorField" PQresultErrorField) + ((res pgsql-result) + (field-code :int)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQresultErrorMessage)) +(uffi:def-function ("PQresultErrorMessage" PQresultErrorMessage) + ((res pgsql-result)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQntuples)) +(uffi:def-function ("PQntuples" PQntuples) + ((res pgsql-result)) + :module "postgresql" + :returning :int) + +(declaim (inline PQnfields)) +(uffi:def-function ("PQnfields" PQnfields) + ((res pgsql-result)) + :module "postgresql" + :returning :int) + +(declaim (inline PQfname)) +(uffi:def-function ("PQfname" PQfname) + ((res pgsql-result) + (field-num :int)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQfnumber)) +(uffi:def-function ("PQfnumber" PQfnumber) + ((res pgsql-result) + (field-name :cstring)) + :module "postgresql" + :returning :int) + +(declaim (inline PQftype)) +(uffi:def-function ("PQftype" PQftype) + ((res pgsql-result) + (field-num :int)) + :module "postgresql" + :returning pgsql-oid) + +(declaim (inline PQfsize)) +(uffi:def-function ("PQfsize" PQfsize) + ((res pgsql-result) + (field-num :int)) + :module "postgresql" + :returning :short) + +(declaim (inline PQcmdStatus)) +(uffi:def-function ("PQcmdStatus" PQcmdStatus) + ((res pgsql-result)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQoidStatus)) +(uffi:def-function ("PQoidStatus" PQoidStatus) + ((res pgsql-result)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQcmdTuples)) +(uffi:def-function ("PQcmdTuples" PQcmdTuples) + ((res pgsql-result)) + :module "postgresql" + :returning :cstring) + +(declaim (inline PQgetvalue)) +(uffi:def-function ("PQgetvalue" PQgetvalue) + ((res pgsql-result) + (tup-num :int) + (field-num :int)) + :module "postgresql" + :returning (* :unsigned-char)) + +(declaim (inline PQgetlength)) +(uffi:def-function ("PQgetlength" PQgetlength) + ((res pgsql-result) + (tup-num :int) + (field-num :int)) + :module "postgresql" + :returning :int) + +(declaim (inline PQgetisnull)) +(uffi:def-function ("PQgetisnull" PQgetisnull) + ((res pgsql-result) + (tup-num :int) + (field-num :int)) + :module "postgresql" + :returning :int) + +(declaim (inline PQclear)) +(uffi:def-function ("PQclear" PQclear) + ((res pgsql-result)) + :module "postgresql" + :returning :void) + +(declaim (inline PQisBusy)) +(uffi:def-function ("PQisBusy" PQisBusy) + ((conn pgsql-conn)) + :module "postgresql" + :returning :int) + + +;;; Large objects support (MB) + +(defconstant +INV_ARCHIVE+ 65536) ; fe-lobj.c +(defconstant +INV_WRITE+ 131072) +(defconstant +INV_READ+ 262144) + +(declaim (inline lo-creat)) +(uffi:def-function ("lo_creat" lo-create) + ((conn pgsql-conn) + (mode :int)) + :module "postgresql" + :returning pgsql-oid) + +(declaim (inline lo-open)) +(uffi:def-function ("lo_open" lo-open) + ((conn pgsql-conn) + (oid pgsql-oid) + (mode :int)) + :module "postgresql" + :returning :int) + +(declaim (inline lo-write)) +(uffi:def-function ("lo_write" lo-write) + ((conn pgsql-conn) + (fd :int) + (data :cstring) + (size :int)) + :module "postgresql" + :returning :int) + +(declaim (inline lo-read)) +(uffi:def-function ("lo_read" lo-read) + ((conn pgsql-conn) + (fd :int) + (data (* :unsigned-char)) + (size :int)) + :module "postgresql" + :returning :int) + +(declaim (inline lo-lseek)) +(uffi:def-function ("lo_lseek" lo-lseek) + ((conn pgsql-conn) + (fd :int) + (offset :int) + (whence :int)) + :module "postgresql" + :returning :int) + +(declaim (inline lo-close)) +(uffi:def-function ("lo_close" lo-close) + ((conn pgsql-conn) + (fd :int)) + :module "postgresql" + :returning :int) + +(declaim (inline lo-unlink)) +(uffi:def-function ("lo_unlink" lo-unlink) + ((conn pgsql-conn) + (oid pgsql-oid)) + :module "postgresql" + :returning :int) diff --git a/db-postgresql/postgresql-loader.lisp b/db-postgresql/postgresql-loader.lisp new file mode 100644 index 0000000..42f6904 --- /dev/null +++ b/db-postgresql/postgresql-loader.lisp @@ -0,0 +1,40 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-loader.sql +;;;; Purpose: PostgreSQL library loader using UFFI +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:pgsql) + + +(defvar *postgresql-supporting-libraries* '("crypt" "c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the PostgresSQL client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *postgresql-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod clsql-sys:database-type-library-loaded ((database-type + (eql :postgresql))) + *postgresql-library-loaded*) + +(defmethod clsql-sys:database-type-load-foreign ((database-type + (eql :postgresql))) + (clsql-uffi:find-and-load-foreign-library "libpq" + :module "postgresql" + :supporting-libraries *postgresql-supporting-libraries*) + (setq *postgresql-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :postgresql) + diff --git a/db-postgresql/postgresql-package.lisp b/db-postgresql/postgresql-package.lisp new file mode 100644 index 0000000..aac6608 --- /dev/null +++ b/db-postgresql/postgresql-package.lisp @@ -0,0 +1,87 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-package.cl +;;;; Purpose: Package definition for low-level PostgreSQL interface +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:pgsql + #-(and :lispworks (not :lispworks4)) + (:nicknames #:postgresql) + (:use #:cl #:clsql-uffi) + (:export + #:pgsql-oid + #:pgsql-conn-status-type + #:pgsql-conn-status-type#connection-ok + #:pgsql-conn-status-type#connection-bad + #:pgsql-exec-status-type + #:pgsql-exec-status-type#empty-query + #:pgsql-exec-status-type#command-ok + #:pgsql-exec-status-type#tuples-ok + #:pgsql-exec-status-type#copy-out + #:pgsql-exec-status-type#copy-in + #:pgsql-exec-status-type#bad-response + #:pgsql-exec-status-type#nonfatal-error + #:pgsql-exec-status-type#fatal-error + #:pgsql-conn + #:pgsql-result + + #:pgsql-ftype#bytea + #:pgsql-ftype#int2 + #:pgsql-ftype#int4 + #:pgsql-ftype#int8 + #:pgsql-ftype#float4 + #:pgsql-ftype#float8 + + ;; Used by PQresultErrorField to get the sql error code + #:+PG-DIAG-SQLSTATE+ + + + ;; Functions + #:PQsetdbLogin + #:PQlogin + #:PQfinish + #:PQstatus + #:PQerrorMessage + #:PQexec + #:PQresultStatus + #:PQresultErrorField ; used to grab the SQLSTATE code from an error + #:PQresultErrorMessage + #:PQntuples + #:PQnfields + #:PQfname + #:PQfnumber + #:PQftype + #:PQfsize + #:PQcmdStatus + #:PQoidStatus + #:PQcmdTuples + #:PQgetvalue + #:PQgetlength + #:PQgetisnull + #:PQclear + #:PQisBusy + + ;;Large Objects (Marc B) + #:lo-create + #:lo-open + #:lo-write + #:lo-read + #:lo-lseek + #:lo-close + #:lo-unlink + ) + (:documentation "This is the low-level interface to PostgreSQL.")) + + diff --git a/db-postgresql/postgresql-sql.lisp b/db-postgresql/postgresql-sql.lisp new file mode 100644 index 0000000..51d5d0f --- /dev/null +++ b/db-postgresql/postgresql-sql.lisp @@ -0,0 +1,448 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-sql.lisp +;;;; Purpose: High-level PostgreSQL interface using UFFI +;;;; Date Started: Feb 2002 +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-postgresql + (:use #:common-lisp #:clsql-sys #:pgsql #:clsql-uffi) + (:export #:postgresql-database) + (:documentation "This is the CLSQL interface to PostgreSQL.")) + +(in-package #:clsql-postgresql) + +;;; Field conversion functions + +(defun make-type-list-for-auto (num-fields res-ptr) + (let ((new-types '())) + (dotimes (i num-fields) + (declare (fixnum i)) + (let* ((type (PQftype res-ptr i))) + (push + (case type + ((#.pgsql-ftype#bytea + #.pgsql-ftype#int2 + #.pgsql-ftype#int4) + :int32) + (#.pgsql-ftype#int8 + :int64) + ((#.pgsql-ftype#float4 + #.pgsql-ftype#float8) + :double) + (otherwise + t)) + new-types))) + (nreverse new-types))) + +(defun canonicalize-types (types num-fields res-ptr) + (if (null types) + nil + (let ((auto-list (make-type-list-for-auto num-fields res-ptr))) + (cond + ((listp types) + (canonicalize-type-list types auto-list)) + ((eq types :auto) + auto-list) + (t + nil))))) + +(defun tidy-error-message (message &optional encoding) + (unless (stringp message) + (setq message (uffi:convert-from-foreign-string message :encoding encoding))) + (let ((message (string-right-trim '(#\Return #\Newline) message))) + (cond + ((< (length message) (length "ERROR:")) + message) + ((string= message "ERROR:" :end1 6) + (string-left-trim '(#\Space) (subseq message 6))) + (t + message)))) + +(defmethod database-initialize-database-type ((database-type + (eql :postgresql))) + t) + +(uffi:def-type pgsql-conn-def pgsql-conn) +(uffi:def-type pgsql-result-def pgsql-result) + + +(defclass postgresql-database (generic-postgresql-database) + ((conn-ptr :accessor database-conn-ptr :initarg :conn-ptr + :type pgsql-conn-def) + (lock + :accessor database-lock + :initform (make-process-lock "conn")))) + +(defmethod database-type ((database postgresql-database)) + :postgresql) + +(defmethod database-name-from-spec (connection-spec (database-type + (eql :postgresql))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional port options tty) + connection-spec + (declare (ignore password options tty)) + (concatenate 'string + (etypecase host + (null "localhost") + (pathname (namestring host)) + (string host)) + (when port + (concatenate 'string + ":" + (etypecase port + (integer (write-to-string port)) + (string port)))) + "/" db "/" user))) + + +(defmethod database-connect (connection-spec (database-type (eql :postgresql))) + (check-connection-spec connection-spec database-type + (host db user password &optional port options tty)) + (destructuring-bind (host db user password &optional port options tty) + connection-spec + (uffi:with-cstrings ((host-native host) + (user-native user) + (password-native password) + (db-native db) + (port-native port) + (options-native options) + (tty-native tty)) + (let ((connection (PQsetdbLogin host-native port-native + options-native tty-native + db-native user-native + password-native))) + (declare (type pgsql-conn-def connection)) + (when (not (eq (PQstatus connection) + pgsql-conn-status-type#connection-ok)) + (let ((pqstatus (PQstatus connection)) + (pqmessage (tidy-error-message (PQerrorMessage connection)))) + (PQfinish connection) + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id pqstatus + :message pqmessage))) + (make-instance 'postgresql-database + :name (database-name-from-spec connection-spec + database-type) + :database-type :postgresql + :connection-spec connection-spec + :conn-ptr connection))))) + + +(defmethod database-disconnect ((database postgresql-database)) + (PQfinish (database-conn-ptr database)) + (setf (database-conn-ptr database) nil) + t) + +(defmethod database-query (query-expression (database postgresql-database) result-types field-names) + (let ((conn-ptr (database-conn-ptr database))) + (declare (type pgsql-conn-def conn-ptr)) + (uffi:with-cstring (query-native query-expression) + (let ((result (PQexec conn-ptr query-native))) + (when (uffi:null-pointer-p result) + (error 'sql-database-data-error + :database database + :expression query-expression + :message (tidy-error-message (PQerrorMessage conn-ptr) (encoding database)))) + (unwind-protect + (case (PQresultStatus result) + ;; User gave a command rather than a query + (#.pgsql-exec-status-type#command-ok + nil) + (#.pgsql-exec-status-type#empty-query + nil) + (#.pgsql-exec-status-type#tuples-ok + (let ((num-fields (PQnfields result))) + (when result-types + (setq result-types + (canonicalize-types result-types num-fields + result))) + (let ((res (loop for tuple-index from 0 below (PQntuples result) + collect + (loop for i from 0 below num-fields + collect + (if (zerop (PQgetisnull result tuple-index i)) + (convert-raw-field + (PQgetvalue result tuple-index i) + (nth i result-types) + :encoding (encoding database)) + nil))))) + (if field-names + (values res (result-field-names num-fields result)) + res)))) + (t + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+) + :message (tidy-error-message + (PQresultErrorMessage result) + (encoding database))))) + (PQclear result)))))) + +(defun result-field-names (num-fields result) + "Return list of result field names." + (let ((names '())) + (dotimes (i num-fields (nreverse names)) + (declare (fixnum i)) + (push (uffi:convert-from-cstring (PQfname result i)) names)))) + +(defmethod database-execute-command (sql-expression + (database postgresql-database)) + (let ((conn-ptr (database-conn-ptr database))) + (declare (type pgsql-conn-def conn-ptr)) + (uffi:with-cstring (sql-native sql-expression) + (let ((result (PQexec conn-ptr sql-native))) + (when (uffi:null-pointer-p result) + (error 'sql-database-data-error + :database database + :expression sql-expression + :message (tidy-error-message (PQerrorMessage conn-ptr) + (encoding database)))) + (unwind-protect + (case (PQresultStatus result) + (#.pgsql-exec-status-type#command-ok + t) + ((#.pgsql-exec-status-type#empty-query + #.pgsql-exec-status-type#tuples-ok) + (warn "Strange result...") + t) + (t + (error 'sql-database-data-error + :database database + :expression sql-expression + :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+) + :message (tidy-error-message + (PQresultErrorMessage result) + (encoding database))))) + (PQclear result)))))) + +(defstruct postgresql-result-set + (res-ptr (uffi:make-null-pointer 'pgsql-result) + :type pgsql-result-def) + (types nil) + (num-tuples 0 :type integer) + (num-fields 0 :type integer) + (tuple-index 0 :type integer)) + +(defmethod database-query-result-set ((query-expression string) + (database postgresql-database) + &key full-set result-types) + (let ((conn-ptr (database-conn-ptr database))) + (declare (type pgsql-conn-def conn-ptr)) + (uffi:with-cstring (query-native query-expression) + (let ((result (PQexec conn-ptr query-native))) + (when (uffi:null-pointer-p result) + (error 'sql-database-data-error + :database database + :expression query-expression + :message (tidy-error-message (PQerrorMessage conn-ptr) + (encoding database)))) + (case (PQresultStatus result) + ((#.pgsql-exec-status-type#empty-query + #.pgsql-exec-status-type#tuples-ok) + (let ((result-set (make-postgresql-result-set + :res-ptr result + :num-fields (PQnfields result) + :num-tuples (PQntuples result) + :types (canonicalize-types + result-types + (PQnfields result) + result)))) + (if full-set + (values result-set + (PQnfields result) + (PQntuples result)) + (values result-set + (PQnfields result))))) + (t + (unwind-protect + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (PQresultErrorField result +PG-DIAG-SQLSTATE+) + :message (tidy-error-message + (PQresultErrorMessage result) + (encoding database))) + (PQclear result)))))))) + +(defmethod database-dump-result-set (result-set (database postgresql-database)) + (let ((res-ptr (postgresql-result-set-res-ptr result-set))) + (declare (type pgsql-result-def res-ptr)) + (PQclear res-ptr) + t)) + +(defmethod database-store-next-row (result-set (database postgresql-database) + list) + (let ((result (postgresql-result-set-res-ptr result-set)) + (types (postgresql-result-set-types result-set))) + (declare (type pgsql-result-def result)) + (if (>= (postgresql-result-set-tuple-index result-set) + (postgresql-result-set-num-tuples result-set)) + nil + (loop with tuple-index = (postgresql-result-set-tuple-index result-set) + for i from 0 below (postgresql-result-set-num-fields result-set) + for rest on list + do + (setf (car rest) + (if (zerop (PQgetisnull result tuple-index i)) + (convert-raw-field + (PQgetvalue result tuple-index i) + (nth i types) + :encoding (encoding database)) + nil)) + finally + (incf (postgresql-result-set-tuple-index result-set)) + (return list))))) + +;;; Large objects support (Marc B) + +(defmethod database-create-large-object ((database postgresql-database)) + (lo-create (database-conn-ptr database) + (logior pgsql::+INV_WRITE+ pgsql::+INV_READ+))) + + +#+mb-original +(defmethod database-write-large-object (object-id (data string) (database postgresql-database)) + (let ((ptr (database-conn-ptr database)) + (length (length data)) + (result nil) + (fd nil)) + (with-transaction (:database database) + (unwind-protect + (progn + (setf fd (lo-open ptr object-id pgsql::+INV_WRITE+)) + (when (>= fd 0) + (when (= (lo-write ptr fd data length) length) + (setf result t)))) + (progn + (when (and fd (>= fd 0)) + (lo-close ptr fd)) + ))) + result)) + +(defmethod database-write-large-object (object-id (data string) (database postgresql-database)) + (let ((ptr (database-conn-ptr database)) + (length (length data)) + (result nil) + (fd nil)) + (database-execute-command "begin" database) + (unwind-protect + (progn + (setf fd (lo-open ptr object-id pgsql::+INV_WRITE+)) + (when (>= fd 0) + (when (= (lo-write ptr fd data length) length) + (setf result t)))) + (progn + (when (and fd (>= fd 0)) + (lo-close ptr fd)) + (database-execute-command (if result "commit" "rollback") database))) + result)) + +;; (MB) the begin/commit/rollback stuff will be removed when with-transaction wil be implemented +;; (KMR) Can't use with-transaction since that function is in high-level code +(defmethod database-read-large-object (object-id (database postgresql-database)) + (let ((ptr (database-conn-ptr database)) + (buffer nil) + (result nil) + (length 0) + (fd nil)) + (unwind-protect + (progn + (database-execute-command "begin" database) + (setf fd (lo-open ptr object-id pgsql::+INV_READ+)) + (when (>= fd 0) + (setf length (lo-lseek ptr fd 0 2)) + (lo-lseek ptr fd 0 0) + (when (> length 0) + (setf buffer (uffi:allocate-foreign-string + length :unsigned t)) + (when (= (lo-read ptr fd buffer length) length) + (setf result (uffi:convert-from-foreign-string + buffer :length length :null-terminated-p nil + :encoding (encoding database))))))) + (progn + (when buffer (uffi:free-foreign-object buffer)) + (when (and fd (>= fd 0)) (lo-close ptr fd)) + (database-execute-command (if result "commit" "rollback") database))) + result)) + +(defmethod database-delete-large-object (object-id (database postgresql-database)) + (lo-unlink (database-conn-ptr database) object-id)) + + +;;; Object listing + + + +(defmethod database-create (connection-spec (type (eql :postgresql))) + (destructuring-bind (host name user password) connection-spec + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "create database ~A" name) database) + (database-disconnect database))))) + +(defmethod database-destroy (connection-spec (type (eql :postgresql))) + (destructuring-bind (host name user password) connection-spec + (let ((database (database-connect (list host "postgres" user password) + type))) + (setf (slot-value database 'clsql-sys::state) :open) + (unwind-protect + (database-execute-command (format nil "drop database ~A" name) database) + (database-disconnect database))))) + + +(defmethod database-probe (connection-spec (type (eql :postgresql))) + (when (find (second connection-spec) (database-list connection-spec type) + :test #'string-equal) + t)) + + +(defun %pg-database-connection (connection-spec) + (check-connection-spec connection-spec :postgresql + (host db user password &optional port options tty)) + (macrolet ((coerce-string (var) + `(unless (typep ,var 'simple-base-string) + (setf ,var (coerce ,var 'simple-base-string))))) + (destructuring-bind (host db user password &optional port options tty) + connection-spec + (coerce-string db) + (coerce-string user) + (let ((connection (PQsetdbLogin host port options tty db user password))) + (declare (type pgsql::pgsql-conn-ptr connection)) + (unless (eq (PQstatus connection) + pgsql-conn-status-type#connection-ok) + ;; Connect failed + (error 'sql-connection-error + :database-type :postgresql + :connection-spec connection-spec + :error-id (PQstatus connection) + :message (PQerrorMessage connection))) + connection)))) + +(defmethod database-reconnect ((database postgresql-database)) + (let ((lock (database-lock database))) + (with-process-lock (lock "Reconnecting") + (with-slots (connection-spec conn-ptr) + database + (setf conn-ptr (%pg-database-connection connection-spec)) + database)))) + +;;; Database capabilities + +(when (clsql-sys:database-type-library-loaded :postgresql) + (clsql-sys:initialize-database-type :database-type :postgresql)) diff --git a/db-sqlite/Makefile b/db-sqlite/Makefile new file mode 100644 index 0000000..7fb8277 --- /dev/null +++ b/db-sqlite/Makefile @@ -0,0 +1,23 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL SQLITE interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean + diff --git a/db-sqlite/sqlite-api.lisp b/db-sqlite/sqlite-api.lisp new file mode 100644 index 0000000..b25653f --- /dev/null +++ b/db-sqlite/sqlite-api.lisp @@ -0,0 +1,322 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-api-uffi.lisp +;;;; Purpose: Low-level SQLite interface using UFFI +;;;; Authors: Aurelio Bignoli and Kevin Rosenberg +;;;; Created: Nov 2003 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli +;;;; and Copyright (c) 2003-2004 by Kevin Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:sqlite + (:use #:common-lisp #:uffi) + (:export + ;;; Conditions + #:sqlite-error + #:sqlite-error-code + #:sqlite-error-message + + ;;; Core API. + #:sqlite-open + #:sqlite-close + + ;;; New API. + #:sqlite-compile + #:sqlite-step + #:sqlite-finalize + + ;;; Extended API. + #:sqlite-get-table + #:sqlite-free-table + #:sqlite-version ; Defined as constant. + #:sqlite-encoding ; Defined as constant. + #:sqlite-last-insert-rowid + + ;;; Utility functions. + #:make-null-row + #:make-null-vm + #:null-row-p + #:sqlite-aref + #:sqlite-raw-aref + #:sqlite-free-row + + ;;; Types. + #:sqlite-row + #:sqlite-row-pointer + #:sqlite-row-pointer-type + #:sqlite-vm-pointer)) + +(in-package #:sqlite) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Return values for sqlite_exec() and sqlite_step() +;;;; +(defconstant SQLITE-OK 0 "Successful result") +(defconstant SQLITE-ERROR 1 "SQL error or missing database") +(defconstant SQLITE-ROW 100 "sqlite_step() has another row ready") +(defconstant SQLITE-DONE 101 "sqlite_step() has finished executing") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Conditions. +;;;; +(define-condition sqlite-error () + ((message :initarg :message :reader sqlite-error-message :initform "") + (code :initarg :code :reader sqlite-error-code)) + (:report (lambda (condition stream) + (let ((code (sqlite-error-code condition))) + (format stream "SQLite error [~A]: ~A" + code (sqlite-error-message condition)))))) + +(defun signal-sqlite-error (code &optional message) + (let ((condition + (make-condition 'sqlite-error + :code code + :message (if message + message + (uffi:convert-from-cstring + (sqlite-error-string code)))))) + (unless (signal condition) + (invoke-debugger condition)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Foreign types definitions. +;;;; +(def-foreign-type errmsg (* :unsigned-char)) +(def-foreign-type sqlite-db :pointer-void) +(def-foreign-type sqlite-vm :pointer-void) +(def-foreign-type string-pointer (* (* :unsigned-char))) +(def-foreign-type sqlite-row-pointer (* (* :unsigned-char))) + +(defvar +null-errmsg-pointer+ (make-null-pointer 'errmsg)) +(defvar +null-string-pointer-pointer+ (make-null-pointer 'string-pointer)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Lisp types used in declarations. +;;;; +(def-type sqlite-db-type sqlite-db) +(def-type sqlite-row string-pointer) +(def-type sqlite-row-pointer-type (* string-pointer)) +(def-type sqlite-vm-pointer (* sqlite-vm)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Library functions. +;;;; +(defmacro def-sqlite-function (name args &key (returning :void)) + `(def-function ,name ,args + :module "sqlite" + :returning ,returning)) + +(def-sqlite-function + "sqlite_error_string" + ((error-code :int)) + :returning :cstring) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Core API. +;;;; +(declaim (inline %open)) +(def-sqlite-function + ("sqlite_open" %open) + ((dbname :cstring) + (mode :int) + (error-message (* errmsg))) + :returning sqlite-db) + +(declaim (inline sqlite-close)) +(def-sqlite-function + "sqlite_close" + ((db sqlite-db))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; New API. +;;;; +(declaim (inline %compile)) +(def-sqlite-function + ("sqlite_compile" %compile) + ((db sqlite-db) + (sql :cstring) + (sql-tail (* (* :unsigned-char))) + (vm (* sqlite-vm)) + (error-message (* errmsg))) + :returning :int) + +(declaim (inline %step)) +(def-sqlite-function + ("sqlite_step" %step) + ((vm sqlite-vm) + (cols-n (* :int)) + (cols (* (* (* :unsigned-char)))) + (col-names (* (* (* :unsigned-char))))) + :returning :int) + +(declaim (inline %finalize)) +(def-sqlite-function + ("sqlite_finalize" %finalize) + ((vm sqlite-vm) + (error-message (* errmsg))) + :returning :int) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Extended API. +;;;; +(declaim (inline sqlite-last-insert-rowid)) +(def-sqlite-function + "sqlite_last_insert_rowid" + ((db sqlite-db)) + :returning :int) + +(declaim (inline %get-table)) +(def-sqlite-function + ("sqlite_get_table" %get-table) + ((db sqlite-db) + (sql :cstring) + (result (* (* (* :unsigned-char)))) + (rows-n (* :int)) + (cols-n (* :int)) + (error-message (* errmsg))) + :returning :int) + +(declaim (inline %free-table)) +(def-sqlite-function + ("sqlite_free_table" %free-table) + ((rows :pointer-void))) + +(declaim (inline sqlite-libversion)) +(def-sqlite-function + "sqlite_libversion" + () + :returning :cstring) + +(declaim (inline sqlite-libencoding)) +(def-sqlite-function + "sqlite_libencoding" + () + :returning :cstring) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Wrapper functions. +;;;; +(defparameter sqlite-version (sqlite-libversion)) +(defparameter sqlite-encoding (sqlite-libencoding)) + +(defun sqlite-open (db-name &optional (mode 0)) + (with-cstring (db-name-native db-name) + (let ((db (%open db-name-native mode +null-errmsg-pointer+))) + (if (null-pointer-p db) + (signal-sqlite-error SQLITE-ERROR + (format nil "unable to open ~A" db-name)) + db)))) + +(defun sqlite-compile (db sql) + (with-cstring (sql-native sql) + (let ((vm (allocate-foreign-object 'sqlite-vm))) + (with-foreign-object (sql-tail '(* :unsigned-char)) + (let ((result (%compile db sql-native sql-tail vm +null-errmsg-pointer+))) + (if (= result SQLITE-OK) + vm + (progn + (free-foreign-object vm) + (signal-sqlite-error result)))))))) + +(defun sqlite-step (vm) + (declare (type sqlite-vm-pointer vm)) + (with-foreign-object (cols-n :int) + (let ((cols (allocate-foreign-object '(* (* :unsigned-char)))) + (col-names (allocate-foreign-object '(* (* :unsigned-char))))) + (declare (type sqlite-row-pointer-type cols col-names)) + (let ((result (%step (deref-pointer vm 'sqlite-vm) + cols-n cols col-names))) + (cond + ((= result SQLITE-ROW) + (let ((n (deref-pointer cols-n :int))) + (values n cols col-names))) + ((= result SQLITE-DONE) + (free-foreign-object cols) + (free-foreign-object col-names) + (values 0 +null-string-pointer-pointer+ +null-string-pointer-pointer+)) + (t + (free-foreign-object cols) + (free-foreign-object col-names) + (signal-sqlite-error result))))))) + +(defun sqlite-finalize (vm) + (declare (type sqlite-vm-pointer vm)) + (let ((result (%finalize (deref-pointer vm 'sqlite-vm) +null-errmsg-pointer+))) + (if (= result SQLITE-OK) + (progn + (free-foreign-object vm) + t) + (signal-sqlite-error result)))) + +(defun sqlite-get-table (db sql) + (declare (type sqlite-db-type db)) + (with-cstring (sql-native sql) + (let ((rows (allocate-foreign-object '(* (* :unsigned-char))))) + (declare (type sqlite-row-pointer-type rows)) + (with-foreign-object (rows-n :int) + (with-foreign-object (cols-n :int) + (let ((result (%get-table db sql-native rows rows-n cols-n +null-errmsg-pointer+))) + (if (= result SQLITE-OK) + (let ((cn (deref-pointer cols-n :int)) + (rn (deref-pointer rows-n :int))) + (values rows rn cn)) + (progn + (free-foreign-object rows) + (signal-sqlite-error result))))))))) + +(declaim (inline sqlite-free-table)) +(defun sqlite-free-table (table) + (declare (type sqlite-row-pointer-type table)) + (%free-table (deref-pointer table 'sqlite-row-pointer))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Utility functions. +;;;; +(declaim (inline make-null-row)) +(defun make-null-row () + +null-string-pointer-pointer+) + +(declaim (inline make-null-vm)) +(defun make-null-vm () + (uffi:make-null-pointer 'sqlite-vm)) + +(declaim (inline null-row-p)) +(defun null-row-p (row) + (null-pointer-p row)) + +(declaim (inline sqlite-aref)) +(defun sqlite-aref (a n encoding) + (declare (type sqlite-row-pointer-type a)) + (convert-from-foreign-string + (deref-array (deref-pointer a 'sqlite-row-pointer) '(:array (* :unsigned-char)) n) + :encoding encoding)) + +(declaim (inline sqlite-raw-aref)) +(defun sqlite-raw-aref (a n) + (declare (type sqlite-row-pointer-type a)) + (deref-array (deref-pointer a 'sqlite-row-pointer) '(:array (* :unsigned-char)) n)) + +(declaim (inline sqlite-free-row)) +(defun sqlite-free-row (row) + (declare (type sqlite-row-pointer-type row)) + (free-foreign-object row)) diff --git a/db-sqlite/sqlite-loader.lisp b/db-sqlite/sqlite-loader.lisp new file mode 100644 index 0000000..ac77faf --- /dev/null +++ b/db-sqlite/sqlite-loader.lisp @@ -0,0 +1,40 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-loader.lisp +;;;; Purpose: SQLite library loader using UFFI +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Nov 2003 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sqlite) + +(defvar *sqlite-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld +to load the SQLite library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *sqlite-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod database-type-library-loaded ((database-type (eql :sqlite))) + "T if foreign library was able to be loaded successfully. " + *sqlite-library-loaded*) + +(defmethod database-type-load-foreign ((database-type (eql :sqlite))) + (clsql-uffi:find-and-load-foreign-library '("libsqlite" "sqlite") + :module "sqlite" + :supporting-libraries *sqlite-supporting-libraries*) + (setq *sqlite-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :sqlite) + + + diff --git a/db-sqlite/sqlite-package.lisp b/db-sqlite/sqlite-package.lisp new file mode 100644 index 0000000..a3282ea --- /dev/null +++ b/db-sqlite/sqlite-package.lisp @@ -0,0 +1,21 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-package.lisp +;;;; Purpose: Package definition for low-level SQLite interface +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-sqlite + (:use #:common-lisp #:clsql-sys) + (:export #:sqlite-database)) diff --git a/db-sqlite/sqlite-sql.lisp b/db-sqlite/sqlite-sql.lisp new file mode 100644 index 0000000..ef622dd --- /dev/null +++ b/db-sqlite/sqlite-sql.lisp @@ -0,0 +1,332 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-sql.lisp +;;;; Purpose: High-level SQLite interface +;;;; Authors: Aurelio Bignoli, Kevin Rosenberg, Marcus Pearce +;;;; Created: Aug 2003 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli and +;;;; Copyright (c) 2003-2010 by Kevin Rosenberg and Marcus Pearce. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sqlite) + +(defclass sqlite-database (database) + ((sqlite-db :initarg :sqlite-db :accessor sqlite-db))) + +(defmethod database-type ((database sqlite-database)) + :sqlite) + +(defmethod database-initialize-database-type ((database-type (eql :sqlite))) + t) + +(defun check-sqlite-connection-spec (connection-spec) + (check-connection-spec connection-spec :sqlite (name))) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :sqlite))) + (check-sqlite-connection-spec connection-spec) + (first connection-spec)) + +(defmethod database-connect (connection-spec (database-type (eql :sqlite))) + (check-sqlite-connection-spec connection-spec) + (handler-case + (make-instance 'sqlite-database + :name (database-name-from-spec connection-spec :sqlite) + :database-type :sqlite + :connection-spec connection-spec + :sqlite-db (sqlite:sqlite-open (first connection-spec))) + (sqlite:sqlite-error (err) + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (sqlite:sqlite-error-code err) + :message (sqlite:sqlite-error-message err))))) + +(defmethod database-disconnect ((database sqlite-database)) + (sqlite:sqlite-close (sqlite-db database)) + (setf (sqlite-db database) nil) + t) + +(defmethod database-execute-command (sql-expression (database sqlite-database)) + (handler-case + (multiple-value-bind (data row-n col-n) + (sqlite:sqlite-get-table (sqlite-db database) sql-expression) + (sqlite:sqlite-free-table data) + (unless (= row-n 0) + (error 'sql-warning + :format-control + "Result set not empty: ~@(~A~) row~:P, ~@(~A~) column~:P " + :format-arguments (list row-n col-n)))) + (sqlite:sqlite-error (err) + (error 'sql-database-data-error + :database database + :expression sql-expression + :error-id (sqlite:sqlite-error-code err) + :message (sqlite:sqlite-error-message err)))) + t) + +(defstruct sqlite-result-set + (vm (sqlite:make-null-vm) + :type sqlite:sqlite-vm-pointer) + (first-row (sqlite:make-null-row) + :type sqlite:sqlite-row-pointer-type) + (col-names (sqlite:make-null-row) + :type sqlite:sqlite-row-pointer-type) + (result-types nil) + (n-col 0 :type fixnum)) + +(defmethod database-query (query-expression (database sqlite-database) result-types field-names) + (declare (optimize (speed 3) (safety 0) (debug 0) (space 0))) + (handler-case + (let ((vm (sqlite:sqlite-compile (sqlite-db database) + query-expression)) + (rows '()) + (col-names '())) + (unwind-protect + ;; Read the first row to get column number and names. + (multiple-value-bind (n-col new-row sqlite-col-names) + (sqlite:sqlite-step vm) + (declare (type sqlite:sqlite-row-pointer-type new-row)) + (when (> n-col 0) + (when field-names + (setf col-names (loop for i from 0 below n-col + collect (sqlite:sqlite-aref sqlite-col-names i (encoding database))))) + (let ((canonicalized-result-types + (canonicalize-result-types result-types n-col sqlite-col-names database))) + (flet ((extract-row-data (row) + (declare (type sqlite:sqlite-row-pointer-type row)) + (loop for i from 0 below n-col + collect (clsql-uffi:convert-raw-field + (sqlite:sqlite-raw-aref row i) + (nth i canonicalized-result-types) + :encoding (encoding database))))) + (push (extract-row-data new-row) rows) + + ;; Read subsequent rows. + (do () (nil) + (multiple-value-bind (n-col new-row) + (sqlite:sqlite-step vm) + (declare (type sqlite:sqlite-row-pointer-type new-row)) + (if (> n-col 0) + (push (extract-row-data new-row) rows) + (return)))))))) + (sqlite:sqlite-finalize vm)) + (values (nreverse rows) col-names)) + (sqlite:sqlite-error (err) + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (sqlite:sqlite-error-code err) + :message (sqlite:sqlite-error-message err))))) + +(defmethod database-query-result-set ((query-expression string) + (database sqlite-database) + &key result-types full-set) + (let ((vm nil)) + (handler-case + (progn + (setf vm (sqlite:sqlite-compile (sqlite-db database) + query-expression)) + ;;; To obtain column number/datatypes we have to read the first row. + (multiple-value-bind (n-col cols col-names) + (sqlite:sqlite-step vm) + (declare (type sqlite:sqlite-row-pointer-type cols)) + (let ((result-set (make-sqlite-result-set + :vm vm + :first-row cols + :n-col n-col + :col-names col-names + :result-types + (canonicalize-result-types + result-types + n-col + col-names + database)))) + (if full-set + (values result-set n-col nil) + (values result-set n-col))))) + (sqlite:sqlite-error (err) + (progn + (when vm + ;; The condition was thrown by sqlite-step, vm must be + ;; deallocated. + (ignore-errors + (sqlite:sqlite-finalize vm))) + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (sqlite:sqlite-error-code err) + :message (sqlite:sqlite-error-message err))11))))) + +(defun canonicalize-result-types (result-types n-col col-names database) + (when result-types + (let ((raw-types (if (eq :auto result-types) + (loop for j from n-col below (* 2 n-col) + collect (ensure-keyword + (sqlite:sqlite-aref col-names j (encoding database)))) + result-types))) + (loop for type in raw-types + collect + (case type + ((:int :integer :tinyint) + :int32) + (:long #+(or x86-64 64bit) :int64 #-(or x86-64 64bit) :int32) + (:bigint + :int64) + ((:float :double) + :double) + ((:numeric) + :number) + (otherwise + :string)))))) + +(defmethod database-dump-result-set (result-set (database sqlite-database)) + (handler-case + (sqlite:sqlite-finalize (sqlite-result-set-vm result-set)) + (sqlite:sqlite-error (err) + (error 'sql-database-error + :message + (format nil "Error finalizing SQLite VM: ~A" + (sqlite:sqlite-error-message err)))))) + +(defmethod database-store-next-row (result-set (database sqlite-database) list) + (let ((n-col (sqlite-result-set-n-col result-set)) + (result-types (sqlite-result-set-result-types result-set))) + (if (= n-col 0) + ;; empty result set + nil + (let ((row (sqlite-result-set-first-row result-set))) + (if (sqlite:null-row-p row) + ;; First row already used. fetch another row from DB. + (handler-case + (multiple-value-bind (n new-row col-names) + (sqlite:sqlite-step (sqlite-result-set-vm result-set)) + (declare (ignore n col-names) + (type sqlite:sqlite-row-pointer-type new-row)) + (if (sqlite:null-row-p new-row) + (return-from database-store-next-row nil) + (setf row new-row))) + (sqlite:sqlite-error (err) + (error 'sql-database-error + :message + (format nil "Error in sqlite-step: ~A" + (sqlite:sqlite-error-message err))))) + + ;; Use the row previously read by database-query-result-set. + (setf (sqlite-result-set-first-row result-set) + (sqlite:make-null-row))) + (loop for i = 0 then (1+ i) + for rest on list + do (setf (car rest) + (clsql-uffi:convert-raw-field + (sqlite:sqlite-raw-aref row i) + (nth i result-types) + :encoding (encoding database)))) + (sqlite:sqlite-free-row row) + t)))) + +;;; Object listing + +(defmethod database-list-tables-and-sequences ((database sqlite-database) &key owner) + (declare (ignore owner)) + ;; Query is copied from .table command of sqlite comamnd line utility. + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name" + database nil nil))) + +(defmethod database-list-tables ((database sqlite-database) &key owner) + (remove-if #'(lambda (s) + (and (>= (length s) 11) + (string-equal (subseq s 0 11) "_CLSQL_SEQ_"))) + (database-list-tables-and-sequences database :owner owner))) + +(defmethod database-list-views ((database sqlite-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name" + database nil nil))) + +(defmethod database-list-indexes ((database sqlite-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name" + database nil nil))) + +(defmethod database-list-table-indexes (table (database sqlite-database) + &key (owner nil)) + (declare (ignore owner)) + (let ((*print-circle* nil)) + (mapcar #'car + (database-query + (format + nil + "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='~A' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' AND tbl_name='~A' ORDER BY name" + table table) + database nil nil)))) + +(declaim (inline sqlite-table-info)) +(defun sqlite-table-info (table database) + (database-query (format nil "PRAGMA table_info('~A')" table) + database nil nil)) + +(defmethod database-list-attributes (table (database sqlite-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'(lambda (table-info) (second table-info)) + (sqlite-table-info table database))) + +(defmethod database-attribute-type (attribute table + (database sqlite-database) + &key (owner nil)) + (declare (ignore owner)) + (loop for field-info in (sqlite-table-info table database) + when (string= attribute (second field-info)) + return + (let* ((raw-type (third field-info)) + (start-length (position #\( raw-type)) + (type (if start-length + (subseq raw-type 0 start-length) + raw-type)) + (length (if start-length + (parse-integer (subseq raw-type (1+ start-length)) + :junk-allowed t) + nil))) + (values (when type (ensure-keyword type)) + length + nil + (if (string-equal (fourth field-info) "0") + 1 0))))) + +(defmethod database-create (connection-spec (type (eql :sqlite))) + (declare (ignore connection-spec)) + ;; databases are created automatically by SQLite + t) + +(defmethod database-destroy (connection-spec (type (eql :sqlite))) + (destructuring-bind (name) connection-spec + (if (probe-file name) + (delete-file name) + nil))) + +(defmethod database-probe (connection-spec (type (eql :sqlite))) + (destructuring-bind (name) connection-spec + ;; TODO: Add a test that this file is a real sqlite database + (or (string-equal ":memory:" name) + (and (probe-file name) t)))) + +;;; Database capabilities + +(defmethod db-type-has-boolean-where? ((db-type (eql :sqlite))) + nil) + + + diff --git a/db-sqlite3/sqlite3-api.lisp b/db-sqlite3/sqlite3-api.lisp new file mode 100644 index 0000000..942246e --- /dev/null +++ b/db-sqlite3/sqlite3-api.lisp @@ -0,0 +1,367 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite3-api.lisp +;;;; Purpose: Low-level SQLite3 interface using UFFI +;;;; Authors: Aurelio Bignoli +;;;; Created: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:sqlite3 + (:use #:common-lisp #:uffi) + (:export + ;;; Conditions + #:sqlite3-error + #:sqlite3-error-code + #:sqlite3-error-message + + ;;; API functions. + #:sqlite3-open + #:sqlite3-close + + #:sqlite3-prepare + #:sqlite3-step + #:sqlite3-finalize + + #:sqlite3-column-count + #:sqlite3-column-name + #:sqlite3-column-type + #:sqlite3-column-text + #:sqlite3-column-bytes + #:sqlite3-column-blob + + ;;; Types. + #:sqlite3-db + #:sqlite3-db-type + #:sqlite3-stmt-type + #:unsigned-char-ptr-type + #:null-stmt + + ;;; Columnt types. + #:SQLITE-INTEGER + #:SQLITE-FLOAT + #:SQLITE-TEXT + #:SQLITE-BLOB + #:SQLITE-NULL)) + +(in-package #:sqlite3) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Return values for sqlite_exec() and sqlite_step() +;;;; +(defconstant SQLITE-OK 0 "Successful result") +(defconstant SQLITE-ERROR 1 "SQL error or missing database") +(defconstant SQLITE-INTERNAL 2 "An internal logic error in SQLite") +(defconstant SQLITE-PERM 3 "Access permission denied") +(defconstant SQLITE-ABORT 4 "Callback routine requested an abort") +(defconstant SQLITE-BUSY 5 "The database file is locked") +(defconstant SQLITE-LOCKED 6 "A table in the database is locked") +(defconstant SQLITE-NOMEM 7 "A malloc() failed") +(defconstant SQLITE-READONLY 8 "Attempt to write a readonly database") +(defconstant SQLITE-INTERRUPT 9 "Operation terminated by sqlite3_interrupt()") +(defconstant SQLITE-IOERR 10 "Some kind of disk I/O error occurred") +(defconstant SQLITE-CORRUPT 11 "The database disk image is malformed") +(defconstant SQLITE-NOTFOUND 12 "(Internal Only) Table or record not found") +(defconstant SQLITE-FULL 13 "Insertion failed because database is full") +(defconstant SQLITE-CANTOPEN 14 "Unable to open the database file") +(defconstant SQLITE-PROTOCOL 15 "Database lock protocol error") +(defconstant SQLITE-EMPTY 16 "Database is empty") +(defconstant SQLITE-SCHEMA 17 "The database schema changed") +(defconstant SQLITE-TOOBIG 18 "Too much data for one row of a table") +(defconstant SQLITE-CONSTRAINT 19 "Abort due to contraint violation") +(defconstant SQLITE-MISMATCH 20 "Data type mismatch") +(defconstant SQLITE-MISUSE 21 "Library used incorrectly") +(defconstant SQLITE-NOLFS 22 "Uses OS features not supported on host") +(defconstant SQLITE-AUTH 23 "Authorization denied") +(defconstant SQLITE-FORMAT 24 "Auxiliary database format error") +(defconstant SQLITE-RANGE 25 "2nd parameter to sqlite3_bind out of range") +(defconstant SQLITE-NOTADB 26 "File opened that is not a database file") +(defconstant SQLITE-ROW 100 "sqlite3_step() has another row ready") +(defconstant SQLITE-DONE 101 "sqlite3_step() has finished executing") + +(defparameter error-codes + (list + (cons SQLITE-OK "not an error") + (cons SQLITE-ERROR "SQL logic error or missing database") + (cons SQLITE-INTERNAL "internal SQLite implementation flaw") + (cons SQLITE-PERM "access permission denied") + (cons SQLITE-ABORT "callback requested query abort") + (cons SQLITE-BUSY "database is locked") + (cons SQLITE-LOCKED "database table is locked") + (cons SQLITE-NOMEM "out of memory") + (cons SQLITE-READONLY "attempt to write a readonly database") + (cons SQLITE-INTERRUPT "interrupted") + (cons SQLITE-IOERR "disk I/O error") + (cons SQLITE-CORRUPT "database disk image is malformed") + (cons SQLITE-NOTFOUND "table or record not found") + (cons SQLITE-FULL "database is full") + (cons SQLITE-CANTOPEN "unable to open database file") + (cons SQLITE-PROTOCOL "database locking protocol failure") + (cons SQLITE-EMPTY "table contains no data") + (cons SQLITE-SCHEMA "database schema has changed") + (cons SQLITE-TOOBIG "too much data for one table row") + (cons SQLITE-CONSTRAINT "constraint failed") + (cons SQLITE-MISMATCH "datatype mismatch") + (cons SQLITE-MISUSE "library routine called out of sequence") + (cons SQLITE-NOLFS "kernel lacks large file support") + (cons SQLITE-AUTH "authorization denied") + (cons SQLITE-FORMAT "auxiliary database format error") + (cons SQLITE-RANGE "bind index out of range") + (cons SQLITE-NOTADB "file is encrypted or is not a database")) + "Association list of error messages.") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Column types. +;;;; +(defconstant SQLITE-INTEGER 1) +(defconstant SQLITE-FLOAT 2) +(defconstant SQLITE-TEXT 3) +(defconstant SQLITE-BLOB 4) +(defconstant SQLITE-NULL 5) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Foreign types definitions. +;;;; +(def-foreign-type sqlite3-db :pointer-void) +(def-foreign-type sqlite3-stmt :pointer-void) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Lisp types definitions. +;;;; +(def-type sqlite3-db-type sqlite3-db) +(def-type sqlite3-db-ptr-type (* sqlite3-db)) +(def-type sqlite3-stmt-type sqlite3-stmt) +(def-type sqlite3-stmt-ptr-type (* sqlite3-stmt)) +(def-type unsigned-char-ptr-type (* :unsigned-char)) + +(defparameter null-stmt (make-null-pointer :void)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Hash tables for db and statement pointers. +;;; +(defvar *db-pointers* (make-hash-table)) +(defvar *stmt-pointers* (make-hash-table)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Conditions. +;;;; +(define-condition sqlite3-error () + ((message :initarg :message :reader sqlite3-error-message :initform "") + (code :initarg :code :reader sqlite3-error-code)) + (:report (lambda (condition stream) + (format stream "Sqlite3 error [~A]: ~A" + (sqlite3-error-code condition) + (sqlite3-error-message condition))))) + +(defgeneric signal-sqlite3-error (db)) +(defmethod signal-sqlite3-error (db) + (let ((condition + (make-condition 'sqlite3-error + :code (sqlite3-errcode db) + :message (convert-from-cstring (sqlite3-errmsg db))))) + (unless (signal condition) + (invoke-debugger condition)))) + +(defmethod signal-sqlite3-error ((code number)) + (let ((condition + (make-condition 'sqlite3-error + :code code + :message (let ((s (cdr (assoc code error-codes)))) + (if s + s + "unknown error"))))) + (unless (signal condition) + (invoke-debugger condition)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Library functions. +;;;; +(defmacro def-sqlite3-function (name args &key (returning :void)) + `(def-function ,name ,args + :module "sqlite3" + :returning ,returning)) + +(declaim (inline %errcode)) +(def-sqlite3-function + "sqlite3_errcode" + ((db sqlite3-db)) + :returning :int) + +(declaim (inline %errmsg)) +(def-sqlite3-function + "sqlite3_errmsg" + ((db sqlite3-db)) + :returning :cstring) + +(declaim (inline %open)) +(def-sqlite3-function + ("sqlite3_open" %open) + ((dbname :cstring) + (db (* sqlite3-db))) + :returning :int) + +(declaim (inline %close)) +(def-sqlite3-function + ("sqlite3_close" %close) + ((db sqlite3-db)) + :returning :int) + +(declaim (inline %prepare)) +(def-sqlite3-function + ("sqlite3_prepare" %prepare) + ((db sqlite3-db) + (sql :cstring) + (len :int) + (stmt (* sqlite3-stmt)) + (sql-tail (* (* :unsigned-char)))) + :returning :int) + +(declaim (inline %step)) +(def-sqlite3-function + ("sqlite3_step" %step) + ((stmt sqlite3-stmt)) + :returning :int) + +(declaim (inline %finalize)) +(def-sqlite3-function + ("sqlite3_finalize" %finalize) + ((stmt sqlite3-stmt)) + :returning :int) + +(declaim (inline sqlite3-column-count)) +(def-sqlite3-function + "sqlite3_column_count" + ((stmt sqlite3-stmt)) + :returning :int) + +(declaim (inline %column-name)) +(def-sqlite3-function + ("sqlite3_column_name" %column-name) + ((stmt sqlite3-stmt) + (n-col :int)) + :returning :cstring) + +(declaim (inline sqlite3-column-type)) +(def-sqlite3-function + "sqlite3_column_type" + ((stmt sqlite3-stmt) + (n-col :int)) + :returning :int) + +(declaim (inline sqlite3-column-text)) +(def-sqlite3-function + "sqlite3_column_text" + ((stmt sqlite3-stmt) + (n-col :int)) + :returning (* :unsigned-char)) + +(declaim (inline sqlite3-column-bytes)) +(def-sqlite3-function + "sqlite3_column_bytes" + ((stmt sqlite3-stmt) + (n-col :int)) + :returning :int) + +(declaim (inline sqlite3-column-blob)) +(def-sqlite3-function + "sqlite3_column_blob" + ((stmt sqlite3-stmt) + (n-col :int)) + :returning :pointer-void) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; wrapper functions. +;;;; +(defun sqlite3-open (db &optional (mode 0) + &aux (db-name (etypecase db + (pathname (namestring db)) + (string db)))) + (declare (ignore mode) (type string db-name)) + (let ((dbp (allocate-foreign-object 'sqlite3-db))) + (declare (type sqlite3-db-ptr-type dbp)) + (with-cstring (db-name-native db-name) + (let ((result (%open db-name-native dbp))) + (if (/= result 0) + (progn + ;; According to docs, the db must be closed even in case + ;; of error. + (%close (deref-pointer dbp 'sqlite3-db)) + (free-foreign-object dbp) + (signal-sqlite3-error result)) + (let ((db (deref-pointer dbp 'sqlite3-db))) + (declare (type sqlite3-db-type db)) + (setf (gethash db *db-pointers*) dbp) + db)))))) + +(declaim (ftype (function (sqlite3-db-type) t) sqlite3-close)) +(defun sqlite3-close (db) + (declare (type sqlite3-db-type db)) + (let ((result (%close db))) + (if (/= result 0) + (signal-sqlite3-error result) + (progn + (free-foreign-object (gethash db *db-pointers*)) + (remhash db *db-pointers*) + t)))) + +(declaim (ftype (function (sqlite3-db-type string) sqlite3-stmt-type) sqlite3-prepare)) +(defun sqlite3-prepare (db sql) + (declare (type sqlite3-db-type db)) + (with-cstring (sql-native sql) + (let ((stmtp (allocate-foreign-object 'sqlite3-stmt))) + (declare (type sqlite3-stmt-ptr-type stmtp)) + (with-foreign-object (sql-tail '(* :unsigned-char)) + (let ((result (%prepare db sql-native -1 stmtp sql-tail))) + (if (/= result SQLITE-OK) + (progn + (unless (null-pointer-p stmtp) + ;; There is an error, but a statement has been allocated: + ;; finalize it (better safe than sorry). + (%finalize (deref-pointer stmtp 'sqlite3-stmt))) + (free-foreign-object stmtp) + (signal-sqlite3-error db)) + (let ((stmt (deref-pointer stmtp 'sqlite3-stmt))) + (declare (type sqlite3-stmt-type stmt)) + (setf (gethash stmt *stmt-pointers*) stmtp) + stmt))))))) + +(declaim (ftype (function (sqlite3-stmt-type) t) sqlite3-step)) +(defun sqlite3-step (stmt) + (declare (type sqlite3-stmt-type stmt)) + (let ((result (%step stmt))) + (cond ((= result SQLITE-ROW) t) + ((= result SQLITE-DONE) nil) + (t (signal-sqlite3-error result))))) + +(declaim (ftype (function (sqlite3-stmt-type) t) sqlite3-finalize)) +(defun sqlite3-finalize (stmt) + (declare (type sqlite3-stmt-type stmt)) + (let ((result (%finalize stmt))) + (if (/= result SQLITE-OK) + (signal-sqlite3-error result) + (progn + (free-foreign-object (gethash stmt *stmt-pointers*)) + (remhash stmt *stmt-pointers*) + t)))) + +(declaim (inline sqlite3-column-name)) +(defun sqlite3-column-name (stmt n) + (declare (type sqlite3-stmt-type stmt) (type fixnum n)) + (convert-from-cstring (%column-name stmt n))) diff --git a/db-sqlite3/sqlite3-loader.lisp b/db-sqlite3/sqlite3-loader.lisp new file mode 100644 index 0000000..90264ea --- /dev/null +++ b/db-sqlite3/sqlite3-loader.lisp @@ -0,0 +1,37 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite3-loader.lisp +;;;; Purpose: Sqlite3 library loader using UFFI +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sqlite3) + +(defvar *sqlite3-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld +to load the Sqlite3 library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *sqlite3-library-loaded* nil + "T if foreign library was able to be loaded successfully") + +(defmethod database-type-library-loaded ((database-type (eql :sqlite3))) + "T if foreign library was able to be loaded successfully. " + *sqlite3-library-loaded*) + +(defmethod database-type-load-foreign ((database-type (eql :sqlite3))) + (clsql-uffi:find-and-load-foreign-library '("libsqlite3" "sqlite3") + :module "sqlite3" + :supporting-libraries *sqlite3-supporting-libraries*) + (setq *sqlite3-library-loaded* t)) + +(clsql-sys:database-type-load-foreign :sqlite3) diff --git a/db-sqlite3/sqlite3-methods.lisp b/db-sqlite3/sqlite3-methods.lisp new file mode 100644 index 0000000..5ce0ac2 --- /dev/null +++ b/db-sqlite3/sqlite3-methods.lisp @@ -0,0 +1,20 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- + +(in-package #:clsql-sys) + + +(defmethod database-pkey-constraint ((class standard-db-class) + (database clsql-sqlite3:sqlite3-database)) + (let* ((keys (keyslots-for-class class)) + (cons (when (= 1 (length keys)) + (view-class-slot-db-constraints (first keys))))) + ;; This method generates primary key constraints part of the table + ;; definition. For Sqlite autoincrement primary keys to work properly + ;; this part of the table definition must be left out (IFF autoincrement) . + (when (or (null cons) ;; didnt have constraints to check + ;; didnt have auto-increment + (null (intersection + +auto-increment-names+ + (listify cons)))) + (call-next-method)))) + diff --git a/db-sqlite3/sqlite3-package.lisp b/db-sqlite3/sqlite3-package.lisp new file mode 100644 index 0000000..2f54f51 --- /dev/null +++ b/db-sqlite3/sqlite3-package.lisp @@ -0,0 +1,21 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-package.lisp +;;;; Purpose: Package definition for low-level SQLite3 interface +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-sqlite3 + (:use #:common-lisp #:clsql-sys) + (:export #:sqlite3-database)) diff --git a/db-sqlite3/sqlite3-sql.lisp b/db-sqlite3/sqlite3-sql.lisp new file mode 100644 index 0000000..8ce592c --- /dev/null +++ b/db-sqlite3/sqlite3-sql.lisp @@ -0,0 +1,353 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-sql.lisp +;;;; Purpose: High-level SQLite3 interface +;;;; Authors: Aurelio Bignoli & Kevin Rosenberg +;;;; Created: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004-2010 by Aurelio Bignoli & Kevin Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sqlite3) + +(defclass sqlite3-database (database) + ((sqlite3-db :initarg :sqlite3-db :accessor sqlite3-db))) + +(defmethod database-type ((database sqlite3-database)) + :sqlite3) + +(defmethod database-initialize-database-type ((database-type (eql :sqlite3))) + t) + +(defun check-sqlite3-connection-spec (connection-spec) + (check-connection-spec connection-spec :sqlite3 (name &optional init-foreign-func))) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :sqlite3))) + (check-sqlite3-connection-spec connection-spec) + (princ-to-string (first connection-spec))) + +(defmethod database-connect (connection-spec (database-type (eql :sqlite3))) + (check-sqlite3-connection-spec connection-spec) + (handler-case + (let ((db (sqlite3:sqlite3-open (first connection-spec))) + (init-foreign-func (second connection-spec))) + (declare (type sqlite3:sqlite3-db-type db)) + (when init-foreign-func + (handler-case + (funcall init-foreign-func db) + (condition (c) + (progn + (sqlite3:sqlite3-close db) + (error c))))) + (make-instance 'sqlite3-database + :name (database-name-from-spec connection-spec :sqlite3) + :database-type :sqlite3 + :connection-spec connection-spec + :sqlite3-db db)) + (sqlite3:sqlite3-error (err) + (error 'sql-connection-error + :database-type database-type + :connection-spec connection-spec + :error-id (sqlite3:sqlite3-error-code err) + :message (sqlite3:sqlite3-error-message err))))) + +(defmethod database-disconnect ((database sqlite3-database)) + (sqlite3:sqlite3-close (sqlite3-db database)) + (setf (sqlite3-db database) nil) + t) + +(defmethod database-execute-command (sql-expression (database sqlite3-database)) + (handler-case + (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database) sql-expression))) + (declare (type sqlite3:sqlite3-stmt-type stmt)) + (when stmt + (unwind-protect + (sqlite3:sqlite3-step stmt) + (sqlite3:sqlite3-finalize stmt)))) + (sqlite3:sqlite3-error (err) + (error 'sql-database-data-error + :database database + :expression sql-expression + :error-id (sqlite3:sqlite3-error-code err) + :message (sqlite3:sqlite3-error-message err)))) + t) + +(defstruct sqlite3-result-set + (stmt sqlite3:null-stmt + :type sqlite3:sqlite3-stmt-type) + (n-col 0 :type fixnum) + (col-names '()) + (result-types '())) + +(declaim (ftype (function (sqlite3:sqlite3-stmt-type fixnum t) list) get-result-types)) +(defun get-result-types (stmt n-col result-types) + (declare (type sqlite3:sqlite3-stmt-type stmt) (type fixnum n-col)) + (if (eq :auto result-types) + (loop for n from 0 below n-col + collect (let ((column-type (sqlite3:sqlite3-column-type stmt n))) + (cond + ((= column-type sqlite3:SQLITE-INTEGER) :int64) + ((= column-type sqlite3:SQLITE-FLOAT) :double) + ((= column-type sqlite3:SQLITE-TEXT) :string) + ((= column-type sqlite3:SQLITE-BLOB) :blob) + ((= column-type sqlite3:SQLITE-NULL) :string) + (t :string)))) + (loop for type in result-types + collect (case type + ((:int :integer :tinyint) :int32) + (:long #+(or x86-64 64bit) :int64 #-(or x86-64 64bit) :int32) + (:bigint :int64) + ((:float :double) :double) + ((:numeric) :number) + (otherwise :string))))) + +(defmethod database-query-result-set ((query-expression string) + (database sqlite3-database) + &key result-types full-set) + (let ((stmt sqlite3:null-stmt)) + (declare (type sqlite3:sqlite3-stmt-type stmt)) + (handler-case + (progn + (setf stmt (sqlite3:sqlite3-prepare (sqlite3-db database) + query-expression)) + (let* ((n-col (if (sqlite3:sqlite3-step stmt) + ;; Non empty result set. + (sqlite3:sqlite3-column-count stmt) + ;; Empty result set. + 0)) + (result-set (make-sqlite3-result-set + :stmt stmt + :n-col n-col + :col-names (loop for n from 0 below n-col + collect (sqlite3:sqlite3-column-name stmt n)) + :result-types (when (> n-col 0) + (get-result-types stmt n-col result-types))))) + (if full-set + (values result-set n-col nil) + (values result-set n-col)))) + (sqlite3:sqlite3-error (err) + (progn + (unless (eq stmt sqlite3:null-stmt) + (ignore-errors + (sqlite3:sqlite3-finalize stmt))) + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (sqlite3:sqlite3-error-code err) + :message (sqlite3:sqlite3-error-message err))))))) + +(defmethod database-dump-result-set (result-set (database sqlite3-database)) + (handler-case + (sqlite3:sqlite3-finalize (sqlite3-result-set-stmt result-set)) + (sqlite3:sqlite3-error (err) + (error 'sql-database-error + :message + (format nil "Error finalizing SQLite3 statement: ~A" + (sqlite3:sqlite3-error-message err)))))) + +(defmethod database-store-next-row (result-set (database sqlite3-database) list) + (let ((n-col (sqlite3-result-set-n-col result-set))) + (if (= n-col 0) + ;; empty result set. + nil + ;; Non-empty set. + (let ((stmt (sqlite3-result-set-stmt result-set))) + (declare (type sqlite3:sqlite3-stmt-type stmt)) + ;; Store row in list. + (loop for i = 0 then (1+ i) + for rest on list + for types = (sqlite3-result-set-result-types result-set) then (rest types) + do (setf (car rest) + (if (eq (first types) :blob) + (clsql-uffi:convert-raw-field + (sqlite3:sqlite3-column-blob stmt i) + (car types) + :length (sqlite3:sqlite3-column-bytes stmt i) + :encoding (encoding database)) + (clsql-uffi:convert-raw-field + (sqlite3:sqlite3-column-text stmt i) + (car types) + :encoding (encoding database))))) + ;; Advance result set cursor. + (handler-case + (unless (sqlite3:sqlite3-step stmt) + (setf (sqlite3-result-set-n-col result-set) 0)) + (sqlite3:sqlite3-error (err) + (error 'sql-database-error + :message (format nil "Error in sqlite3-step: ~A" + (sqlite3:sqlite3-error-message err))))) + t)))) + + +(defmethod database-query (query-expression (database sqlite3-database) result-types field-names) + (declare (optimize (speed 3) (safety 0) (debug 0) (space 0))) + (handler-case + (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database) + query-expression)) + (rows '()) + (col-names '())) + (declare (type sqlite3:sqlite3-stmt-type stmt)) + (unwind-protect + (when (sqlite3:sqlite3-step stmt) + (let ((n-col (sqlite3:sqlite3-column-count stmt))) + (flet ((extract-row-data () + (loop for i from 0 below n-col + for types = (get-result-types stmt n-col result-types) then (rest types) + collect (if (eq (first types) :blob) + (clsql-uffi:convert-raw-field + (sqlite3:sqlite3-column-blob stmt i) + (car types) + :length (sqlite3:sqlite3-column-bytes stmt i) + :encoding (encoding database)) + (clsql-uffi:convert-raw-field + (sqlite3:sqlite3-column-text stmt i) + (car types) + :encoding (encoding database)))))) + (when field-names + (setf col-names (loop for n from 0 below n-col + collect (sqlite3:sqlite3-column-name stmt n)))) + (push (extract-row-data) rows) + (do* () (nil) + (if (sqlite3:sqlite3-step stmt) + (push (extract-row-data) rows) + (return)))))) + (sqlite3:sqlite3-finalize stmt)) + (values (nreverse rows) col-names)) + (sqlite3:sqlite3-error (err) + (error 'sql-database-data-error + :database database + :expression query-expression + :error-id (sqlite3:sqlite3-error-code err) + :message (sqlite3:sqlite3-error-message err))))) + +;;; Object listing + +(defmethod database-list-tables-and-sequences ((database sqlite3-database) &key owner) + (declare (ignore owner)) + ;; Query is copied from .table command of sqlite3 command line utility. + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name" + database nil nil))) + +(defmethod database-list-tables ((database sqlite3-database) &key owner) + (remove-if #'(lambda (s) + (and (>= (length s) 11) + (string-equal (subseq s 0 11) "_CLSQL_SEQ_"))) + (database-list-tables-and-sequences database :owner owner))) + +(defmethod database-list-views ((database sqlite3-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name" + database nil nil))) + +(defmethod database-list-indexes ((database sqlite3-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'car (database-query + "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name" + database nil nil))) + +(defmethod database-list-table-indexes (table (database sqlite3-database) + &key (owner nil)) + (declare (ignore owner)) + (let ((*print-circle* nil)) + (mapcar #'car + (database-query + (format + nil + "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='~A' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' AND tbl_name='~A' ORDER BY name" + table table) + database nil nil)))) + +(declaim (inline sqlite3-table-info)) +(defun sqlite3-table-info (table database) + (let ((sql (format nil "PRAGMA table_info('~A')" + (clsql-sys::unescaped-database-identifier table)))) + (database-query sql database nil nil))) + +(defmethod database-list-attributes (table (database sqlite3-database) + &key (owner nil)) + (declare (ignore owner)) + (mapcar #'(lambda (table-info) (second table-info)) + (sqlite3-table-info table database))) + +(defmethod database-attribute-type (attribute table + (database sqlite3-database) + &key (owner nil)) + (declare (ignore owner)) + + (loop for field-info in (sqlite3-table-info table database) + when (string= (clsql-sys::unescaped-database-identifier attribute) + (second field-info)) + return + (let* ((raw-type (third field-info)) + (start-length (position #\( raw-type)) + (type (string-trim clsql-sys::+whitespace-chars+ + (if start-length + (subseq raw-type 0 start-length) + raw-type))) + (length (if start-length + (parse-integer (subseq raw-type (1+ start-length)) + :junk-allowed t) + nil))) + (values (when type (ensure-keyword type)) + length + nil + (if (string-equal (fourth field-info) "0") + 1 0))))) + +(defmethod database-last-auto-increment-id ((database sqlite3-database) table column) + (declare (ignore table column)) + (car (query "SELECT LAST_INSERT_ROWID();" + :flatp t :field-names nil + :database database))) + +(defmethod database-create (connection-spec (type (eql :sqlite3))) + (declare (ignore connection-spec)) + ;; databases are created automatically by Sqlite3 + t) + +(defmethod database-destroy (connection-spec (type (eql :sqlite3))) + (destructuring-bind (name) connection-spec + (if (probe-file name) + (delete-file name) + nil))) + +(defmethod database-probe (connection-spec (type (eql :sqlite3))) + (destructuring-bind (name) connection-spec + ;; TODO: Add a test that this file is a real sqlite3 database + (or (string-equal ":memory:" name) + (and (probe-file name) t)))) + +(defmethod database-get-type-specifier ((type (eql 'integer)) + args database + (db-type (eql :sqlite3))) + (declare (ignore database)) + (if args + (format nil "INTEGER(~A)" (car args)) + "INTEGER")) + +(defmethod database-get-type-specifier ((type (eql 'integer)) + args database + (db-type (eql :sqlite3))) + (declare (ignore database)) + (if args + (format nil "INTEGER(~A)" (car args)) + "INTEGER")) + +;;; Database capabilities + +(defmethod db-type-has-boolean-where? ((db-type (eql :sqlite3))) + nil) + +(defmethod db-type-has-auto-increment? ((db-type (eql :sqlite3))) + t) diff --git a/doc/COPYING.GFDL b/doc/COPYING.GFDL new file mode 100644 index 0000000..99ab861 --- /dev/null +++ b/doc/COPYING.GFDL @@ -0,0 +1,330 @@ + GNU Free Documentation License + Version 1.1, March 2000 + + Copyright (C) 2000 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +written document "free" in the sense of freedom: to assure everyone +the effective freedom to copy and redistribute it, with or without +modifying it, either commercially or noncommercially. Secondarily, +this License preserves for the author and publisher a way to get +credit for their work, while not being considered responsible for +modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work that contains a +notice placed by the copyright holder saying it can be distributed +under the terms of this License. The "Document", below, refers to any +such manual or work. Any member of the public is a licensee, and is +addressed as "you". + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (For example, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, whose contents can be viewed and edited directly and +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup has been designed to thwart or discourage +subsequent modification by readers is not Transparent. A copy that is +not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML designed for human modification. Opaque formats include +PostScript, PDF, proprietary formats that can be read and edited only +by proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML produced by some word processors for output +purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies of the Document numbering more than 100, +and the Document's license notice requires Cover Texts, you must enclose +the copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a publicly-accessible computer-network location containing a complete +Transparent copy of the Document, free of added material, which the +general network-using public has access to download anonymously at no +charge using public-standard network protocols. If you use the latter +option, you must take reasonably prudent steps, when you begin +distribution of Opaque copies in quantity, to ensure that this +Transparent copy will remain thus accessible at the stated location +until at least one year after the last time you distribute an Opaque +copy (directly or through your agents or retailers) of that edition to +the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has less than five). +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section entitled "History", and its title, and add to + it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. In any section entitled "Acknowledgements" or "Dedications", + preserve the section's title, and preserve in the section all the + substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section as "Endorsements" + or to conflict in title with any Invariant Section. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections entitled "History" +in the various original documents, forming one section entitled +"History"; likewise combine any sections entitled "Acknowledgements", +and any sections entitled "Dedications". You must delete all sections +entitled "Endorsements." + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, does not as a whole count as a Modified Version +of the Document, provided no compilation copyright is claimed for the +compilation. Such a compilation is called an "aggregate", and this +License does not apply to the other self-contained works thus compiled +with the Document, on account of their being thus compiled, if they +are not themselves derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one quarter +of the entire aggregate, the Document's Cover Texts may be placed on +covers that surround only the Document within the aggregate. +Otherwise they must appear on covers around the whole aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License provided that you also include the +original English version of this License. In case of a disagreement +between the translation and the original English version of this +License, the original English version will prevail. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..59a524b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,148 @@ +#!/usr/bin/make +############################################################################### +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for the clsql documentation +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +############################################################################### + +DOCFILE_BASE_DEFAULT:=clsql +DOCFILE_EXT_DEFAULT:=xml + + +# Standard docfile processing + +DEBIAN=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*Debian.*') +SUSE=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*SuSE.*') +SUSE91=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*SuSE Linux 9.1.*') +REDHAT=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*Red Hat.*') +MANDRAKE=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*Mandrake.*') +DARWIN=$(shell expr "`uname -a`" : '.*Darwin.*') +UBUNTU=$(shell expr "`cat /etc/issue 2> /dev/null`" : '.*Ubuntu.*') + + +ifneq (${DEBIAN},0) +OS:=debian +else + ifneq (${SUSE91},0) + OS=suse91 + else + ifneq (${SUSE},0) + OS=suse + else + ifneq (${REDHAT},0) + OS=redhat + else + ifneq (${MANDRAKE},0) + OS=mandrake + else + ifneq (${DARWIN},0) + OS=darwin + else + ifneq (${UBUNTU},0) + OS:=debian + endif + endif + endif + endif + endif + endif +endif + +ifndef DOCFILE_BASE +DOCFILE_BASE=${DOCFILE_BASE_DEFAULT} +endif + +ifndef DOCFILE_EXT +DOCFILE_EXT=${DOCFILE_EXT_DEFAULT} +endif + +DOCFILE:=${DOCFILE_BASE}.${DOCFILE_EXT} +FOFILE:=${DOCFILE_BASE}.fo +PDFFILE:=${DOCFILE_BASE}.pdf +PSFILE:=${DOCFILE_BASE}.ps +DVIFILE:=${DOCFILE_BASE}.dvi +TXTFILE:=${DOCFILE_BASE}.txt +HTMLFILE:=${DOCFILE_BASE}.html +TMPFILES:=${DOCFILE_BASE}.aux ${DOCFILE_BASE}.out ${DOCFILE_BASE}.log +DOCFILES:=$(shell echo *.xml *.xsl) + +ifeq ($(XSLTPROC),) + XSLTPROC:=xsltproc +endif + +CATALOG:=`pwd`/catalog-${OS}.xml +CHECK:=XML_CATALOG_FILES="$(CATALOG)" xmllint --noout --xinclude --postvalid $(DOCFILE) || exit 1 + +.PHONY: all +all: html pdf + +.PHONY: dist +dist: html pdf + +.PHONY: doc +doc: html pdf + +.PHONY: check +check: + @echo "Operating system detected: ${OS}" + @$(CHECK) + +.PHONY: html +html: html.tar.gz + +html.tar.gz: $(DOCFILES) Makefile + @rm -rf html + @mkdir html + @XML_CATALOG_FILES="$(CATALOG)" $(XSLTPROC) --stringparam chunker.output.encoding UTF-8 \ + --xinclude --output html/ html_chunk.xsl $(DOCFILE) + @GZIP='-9' tar czf html.tar.gz html + +.PHONY: fo +fo: ${FOFILE} + +${FOFILE}: $(DOCFILES) Makefile + @XML_CATALOG_FILES="$(CATALOG)" xsltproc --xinclude --stringparam fop1.extensions 1 --output $(FOFILE) fo.xsl $(DOCFILE) + +.PHONY: pdf +pdf: ${PDFFILE} + +${PDFFILE}: ${DOCFILES} Makefile + @$(MAKE) fo + @fop $(FOFILE) -pdf $(PDFFILE) > /dev/null + +.PHONY: dvi +dvi: ${DVIFILE} + +.PHONY: ps +ps: ${PSFILE} + +${PSFILE}: ${DOCFILES} Makefile + @$(MAKE) fo + @fop $(FOFILE) -ps $(PSFILE) > /dev/null + + +.PHONY: txt +txt: ${TXTFILE} + +${TXTFILE}: ${FOFILE} + @XML_CATALOG_FILES="$(CATALOG)" xsltproc --xinclude --output ${HTMLFILE} html.xsl $(DOCFILE) + lynx -dump ${HTMLFILE} > ${TXTFILE} + +.PHONY: clean +clean: + @rm -f *~ *.bak *.orig \#*\# .\#* texput.log + @rm -rf html ${PSFILE} ${HTMLFILE} + @rm -f ${TMPFILES} ${FOFILE} + @rm -f ${DVIFILE} ${TXTFILE} + +.PHONY: distclean +distclean: clean diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..5c0c268 --- /dev/null +++ b/doc/README @@ -0,0 +1,24 @@ +Building the documentation: + +You will need the following packages: + * xsltproc + * docbook + * docbook-xml + * docbook-xsl + * docbook-xsl-doc-html + * fop + +These are the debian/ubuntu package names; on other systems there are probably similar. + + +General Build: +> make + +Check the validity of the source +> make check + +Build just the html: +> make html + +Build just the pdf: +> make pdf diff --git a/doc/TODO b/doc/TODO new file mode 100644 index 0000000..b9f9f97 --- /dev/null +++ b/doc/TODO @@ -0,0 +1,31 @@ +DOCUMENTATION TO DO LIST + +1. Variances from CommonSQL (Ideally, this will be extremely minimal) + +2. Underdocumented CommonSQL features: + + - Retrieval immediate + - SQL operators: ||, SUBSTR, MINUS, INTERSECT, UNION, NVL + +3. CLSQL extensions to CommonSQL + + - SQL operators: group-by, limit, not-null, ==, is, having, the, uplike, + view-class, coalesce, except, exists, substring, concat + + - SELECT: additional keyword arguments accepted include :INNER-JOIN and :ON. + +4. Documenting lower level, non-CommonSQL functions (some of this is already + done). + + - connection pools + - database-query-result-set + +5. Notes on any peculiarities of each of the backends (e.g., unsupported + features, notable extensions etc.). + + - MYSQL + - SQLITE + - ODBC + - ORACLE + +6. Incorporate the threading-warnings.txt into the docs. diff --git a/doc/appendix.xml b/doc/appendix.xml new file mode 100644 index 0000000..f06f035 --- /dev/null +++ b/doc/appendix.xml @@ -0,0 +1,884 @@ + + +%myents; +]> + + + Database Back-ends + + + How CLSQL finds and loads foreign libraries + + For some database types CLSQL has to load external foreign + libaries. These are usually searched for in the standard + locations the operating system uses but you can tell &clsql; to + look into other directories as well by using the function + CLSQL:PUSH-LIBRARY-PATH or by directly + manipulating the special variable + CLSQL:*FOREIGN-LIBRARY-SEARCH-PATHS*. If, + say, the shared library libpq.so needed for PostgreSQL support + is located in the directory /opt/foo/ on + your machine you'd use + + (clsql:push-library-path "/opt/foo/") + + before loading the CLSQL-POSTGRESQL module. (Note the trailing + slash above!) + + If you want to combine this with fully automatic loading of + libraries via ASDF a technique like the following works: + + + (defmethod asdf:perform :after ((o asdf:load-op) + (c (eql (asdf:find-system 'clsql)))) + (funcall (find-symbol (symbol-name '#:push-library-path) + (find-package 'clsql)) + #p"/opt/foo/")) + + + + + Additionally, site-specific initialization can be done using an +initialization file. If the file /etc/clsql-init.lisp +exists, this file will be read after the &clsql; ASDF system is loaded. +This file can contain forms to set site-specific paths as well as change +&clsql; default values. + + + + PostgreSQL + + Libraries + The PostgreSQL back-end requires the PostgreSQL C + client library (libpq.so). The + location of this library is specified via + *postgresql-so-load-path*, which defaults + to /usr/lib/libpq.so. Additional flags + to ld needed for linking are + specified via *postgresql-so-libraries*, + which defaults to ("-lcrypt" "-lc"). + + + Initialization + Use + +(asdf:operate 'asdf:load-op 'clsql-postgresql) + + to load the PostgreSQL back-end. The database type for the + PostgreSQL back-end is :postgresql. + + + Connection Specification + + Syntax of connection-spec + + (host db user password &optional port options tty) + + + + Description of connection-spec + + For every parameter in the connection-spec, + nil indicates that the PostgreSQL default + environment variables (see PostgreSQL documentation) will + be used, or if those are unset, the compiled-in defaults + of the C client library are used. + + + + host + + String representing the hostname or IP address + the PostgreSQL server resides on. Use the empty + string to indicate a connection to localhost via + Unix-Domain sockets instead of TCP/IP. + + + + db + + String representing the name of the database on + the server to connect to. + + + + user + + String representing the user name to use for + authentication. + + + + password + + String representing the unencrypted password to + use for authentication. + + + + port + + String representing the port to use for + communication with the PostgreSQL server. + + + + options + + String representing further runtime options for + the PostgreSQL server. + + + + tty + + String representing the tty or file to use for + debugging messages from the PostgreSQL server. + + + + + + Notes + None. + + + + + PostgreSQL Socket + + Libraries + The PostgreSQL Socket back-end needs + no access to the PostgreSQL C + client library, since it communicates directly with the + PostgreSQL server using the published frontend/backend + protocol, version 2.0. This eases installation and makes it + possible to dump CMU CL images containing CLSQL and this + backend, contrary to backends which require FFI code. + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-postgresql-socket) + + to load the PostgreSQL Socket back-end. The database type + for the PostgreSQL Socket back-end is + :postgresql-socket. + + + + Connection Specification + + Syntax of connection-spec + + (host db user password &optional port options tty) + + + + Description of connection-spec + + + host + + If this is a string, it represents the hostname or + IP address the PostgreSQL server resides on. In + this case communication with the server proceeds via + a TCP connection to the given host and port. + + If this is a pathname, then it is assumed to name the + directory that contains the server's Unix-Domain + sockets. The full name to the socket is then + constructed from this and the port number passed, + and communication will proceed via a connection to + this unix-domain socket. + + + + db + + String representing the name of the database on + the server to connect to. + + + + user + + String representing the user name to use for + authentication. + + + + password + + String representing the unencrypted password to + use for authentication. This can be the empty + string if no password is required for + authentication. + + + + port + + Integer representing the port to use for + communication with the PostgreSQL server. This + defaults to 5432. + + + + options + + String representing further runtime options for + the PostgreSQL server. + + + + tty + + String representing the tty or file to use for + debugging messages from the PostgreSQL server. + + + + + + Notes + None. + + + + + MySQL + + Libraries + The &mysql; back-end requires the &mysql; C + client library (libmysqlclient.so). + The location of this library is specified + via *mysql-so-load-path*, which defaults + to /usr/lib/libmysqlclient.so. + Additional flags to ld needed for + linking are specified via *mysql-so-libraries*, + which defaults to ("-lc"). + + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-mysql) + + to load the &mysql; back-end. The database type for the MySQL + back-end is :mysql. + + + + Connection Specification + + Syntax of connection-spec + (host db user password &optional port) + + + Description of connection-spec + + + host + + String representing the hostname or IP address + the &mysql; server resides on, or nil + to indicate the localhost. + + + + db + + String representing the name of the database on + the server to connect to. + + + + user + + String representing the user name to use for + authentication, or nil to use the + current Unix user ID. + + + + password + + String representing the unencrypted password to + use for authentication, or nil if + the authentication record has an empty password + field. + + + + port + + String representing the port to use for + communication with the MySQL server. + + + + + + Notes + FDDL + + + + drop-index + requires a table to be specified with the + :on keyword parameter. + + + + + views are not + supported by &mysql;. + + + + + The :transactions keyword argument to + create-table + controls whether or not the created table is an InnoDB + table which supports transactions. + + + + + The :owner keyword argument to the FDDL functions + for listing and testing for database objects is ignored. + + + + + FDML + + + + Prior to version 4.1, &mysql; does not support nested + subqueries in calls to select. + + + + + Symbolic SQL Syntax + + + + &mysql; does not support the || + concatenation operator. Use concat + instead. + + + + + &mysql; does not support the substr + operator. Use substring instead. + + + + + &mysql; does not support the + intersect and + except set operations. + + + + + &mysql; (version 4.0 and later) does not support string + table aliases unless the server is started with + ANSI_QUOTES enabled. + + + + + + + + + &odbc; + + Libraries + + The &odbc; back-end requires access to an &odbc; driver + manager as well as &odbc; drivers for the underlying + database server. &clsql; has been tested with + unixODBC ODBC Driver Manager as + well as Microsoft's ODBC manager. These driver managers + have been tested with the + psqlODBC driver for + &postgresql; and the + MyODBC driver for &mysql;. + + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-odbc) + + to load the &odbc; back-end. The database type for the &odbc; + back-end is :odbc. + + + + Connection Specification + + Syntax of connection-spec + (dsn user password &key connection-string) + + + Description of connection-spec + + + dsn + + String representing the ODBC data source name. + + + + user + + String representing the user name to use for + authentication. + + + + password + + String representing the unencrypted password to + use for authentication. + + + + connection-string + + Raw connection string passed to the underlying + ODBC driver. Allows bypassing creating a DSN on the + server. + + + + + + + Notes + FDDL + + + + The :owner keyword argument to the FDDL functions + for listing and testing for database objects is ignored. + + + + + + Connect Examples + + +;; assumes a "mssql" DSN is configured on the lisp host, specifying database server +;; and database name. +> (clsql:connect '("mssql" "database-user" "database-password") + :database-type :odbc) +=> #<CLSQL-ODBC:ODBC-DATABASE mssql/database-user OPEN {100756D123}> + +;; no DSN on the lisp host, specify connection information via :connection-string +> (clsql:connect '("friendly-server-name" "friendly-username" "" + :connection-string "DRIVER={FreeTDS};SERVER=mssql-server;DATABASE=database-name;UID=database-user;PWD=database-password;PORT=1433;TDS_Version=8.0;APP=clsql") + :database-type :odbc) +=> #<CLSQL-ODBC:ODBC-DATABASE friendly-server-name/friendly-username OPEN {100756D123}> + + The friendly-server-name + and friendly-username are only used when + printing the connection object to a stream. + + + + + + &aodbc; + + Libraries The &aodbc; back-end requires + access to the &odbc; interface of &acl; named DBI. This + interface is not available in the trial version of + &acl; + + + Initialization + + Use + +(require 'aodbc-v2) +(asdf:operate 'asdf:load-op 'clsql-aodbc) + + to load the &aodbc; back-end. The database type for the &aodbc; + back-end is :aodbc. + + + + Connection Specification + + Syntax of connection-spec + + (dsn user password) + + + + Description of connection-spec + + + dsn + + String representing the ODBC data source name. + + + + user + + String representing the user name to use for + authentication. + + + + password + + String representing the unencrypted password to + use for authentication. + + + + + + Notes + + None. + + + + + + &sqlite; + + Libraries The &sqlite; back-end requires + the &sqlite; shared library file. Its default file name is + /usr/lib/libsqlite.so. + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-sqlite) + + to load the &sqlite; back-end. The database type for the &sqlite; + back-end is :sqlite. + + + + Connection Specification + + Syntax of connection-spec + (filename) + + + Description of connection-spec + + + filename + + String or pathname representing the filename of + the &sqlite; database file. + + + + + + Notes + Connection + + + + Passing filename a value of + :memory: will create a database in + physical memory instead of using a file on disk. + + + + + Some operations will be many times faster if database + integrity checking is disabled by setting the SYNCHRONOUS + flag to OFF (see the SQLITE manual for details). + + + + + FDDL + + + + The :owner keyword argument to the FDDL functions + for listing and testing for database objects is ignored. + + + + + The :column-list keyword argument to + create-view + is not supported by &sqlite;. + + + + + Symbolic SQL Syntax + + + + &sqlite; does not support the all, + some, any and + exists subquery operations. + + + + + + + + + &sqlite3; + + Libraries The &sqlite3; back-end requires + the &sqlite3; shared library file. Its default file name is + /usr/lib/libsqlite3.so. + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-sqlite3) + + to load the &sqlite3; back-end. The database type for the &sqlite3; + back-end is :sqlite3. + + + + Connection Specification + + Syntax of connection-spec + (filename &optional init-function) + + + Description of connection-spec + + + filename + + String representing the filename of the &sqlite3; + database file. + + + + init-function + + + A function designator. + init-function takes a + single argument of type + sqlite3:sqlite3-db, a foreign pointer to + the C descriptor of the newly opened database. + init-function is called by + the back-end immediately after &sqlite3; + sqlite3_open library function, + and can be used to perform optional database + initializations by calling foreign functions in the + &sqlite3; library. + + + An example of an initialization function which + defines a new collating sequence for text columns is + provided in + ./examples/sqlite3/init-func/. + + + + + + + Notes + Connection + + + + Passing filename a value of + :memory: will create a database in + physical memory instead of using a file on disk. + + + + + Some operations will be many times faster if database + integrity checking is disabled by setting the SYNCHRONOUS + flag to OFF (see the SQLITE manual for details). + + + + + FDDL + + + + The :owner keyword argument to the FDDL functions + for listing and testing for database objects is ignored. + + + + + The :column-list keyword argument to + create-view + is not supported by &sqlite3;. + + + + + Symbolic SQL Syntax + + + + &sqlite3; does not support the all, + some, any and + exists subquery operations. + + + + + + + + + Oracle + + Libraries + The &oracle; back-end requires the &oracle; OCI client + library. (libclntsh.so). The location of + this library is specified relative to the + ORACLE_HOME value in the operating system + environment. + + + + Library Versions + + &clsql; has tested sucessfully using the client library from + Oracle 9i and Oracle 10g server installations as well as + Oracle's 10g Instant Client library. For Oracle 8 and earlier + versions, there is vestigial support by pushing the symbol + :oci7 onto cl:*features* + prior to loading the clsql-oracle &asdf; + system. + + (push :oci7 cl:*features*) + (asdf:operate 'asdf:load-op 'clsql-oracle) + + + + + Initialization + + Use + +(asdf:operate 'asdf:load-op 'clsql-oracle) + + to load the &oracle; back-end. The database type for the Oracle + back-end is :oracle. + + + + Connection Specification + + Syntax of connection-spec + (global-name user password) + + + Description of connection-spec + + + global-name + + String representing the global name of the Oracle database. + This is looked up through the tnsnames.ora file. + + + + user + + String representing the user name to use for + authentication. + + + + password + + String representing the password to + use for authentication.. + + + + + + Notes + Symbolic SQL Syntax + + + + The userenv operator is &oracle; specific. + + + + + &oracle; does not support the except + operator. Use minus instead. + + + + + &oracle; does not support the all, + some, any + subquery operations. + + + + + Transactions + + + + By default, &clsql; starts in transaction AUTOCOMMIT mode + (see set-autocommit). + To begin a transaction in autocommit mode, start-transaction + has to be called explicitly. + + + + + + + + diff --git a/doc/bookinfo.xml b/doc/bookinfo.xml new file mode 100644 index 0000000..2cb4d55 --- /dev/null +++ b/doc/bookinfo.xml @@ -0,0 +1,64 @@ + + +%myents; +]> + + + &clsql; Users' Guide + + + Kevin + M. + Rosenberg + Maintainer of &clsql; + + + Marcus + T. + Pearce + Contributor to &clsql; + + + Pierre + R. + Mai + Author of Original MaiSQL Code + + + onShore Development, Inc. + Author of UncommonSQL Package + + + + + + &clsql; is Copyright © 2002-2010 by Kevin M. Rosenberg, + Copyright © 1999-2001 by Pierre R. Mai, and Copyright + © 1999-2003 onShore Development, Inc. + + + Allegro CL® is a registered + trademark of Franz Inc. + + + Common SQL, + LispWorks are trademarks or + registered trademarks of LispWorks Ltd. + + + Oracle® is a registered + trademark of Oracle Inc. + + + Microsoft Windows® is a + registered trademark of Microsoft Inc. + + + Other brand or product names are the registered + trademarks or trademarks of their respective holders. + + + + diff --git a/doc/catalog-darwin.xml b/doc/catalog-darwin.xml new file mode 100644 index 0000000..0ed575f --- /dev/null +++ b/doc/catalog-darwin.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/catalog-debian.xml b/doc/catalog-debian.xml new file mode 100644 index 0000000..b924d99 --- /dev/null +++ b/doc/catalog-debian.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/catalog-mandrake.xml b/doc/catalog-mandrake.xml new file mode 100644 index 0000000..e797700 --- /dev/null +++ b/doc/catalog-mandrake.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/catalog-redhat.xml b/doc/catalog-redhat.xml new file mode 100644 index 0000000..3b2d92f --- /dev/null +++ b/doc/catalog-redhat.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/catalog-suse.xml b/doc/catalog-suse.xml new file mode 100644 index 0000000..9c772d6 --- /dev/null +++ b/doc/catalog-suse.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/catalog-suse91.xml b/doc/catalog-suse91.xml new file mode 100644 index 0000000..2affd4c --- /dev/null +++ b/doc/catalog-suse91.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/clsql.pdf b/doc/clsql.pdf new file mode 100644 index 0000000..4922a96 Binary files /dev/null and b/doc/clsql.pdf differ diff --git a/doc/clsql.xml b/doc/clsql.xml new file mode 100644 index 0000000..5c81a2a --- /dev/null +++ b/doc/clsql.xml @@ -0,0 +1,30 @@ + + + +%myents; +%xinclude; +]> + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/csql.xml b/doc/csql.xml new file mode 100644 index 0000000..f494441 --- /dev/null +++ b/doc/csql.xml @@ -0,0 +1,749 @@ + + +%myents; +]> + + + &commonsql; Tutorial + Based on the &usql; Tutorial + + + Introduction + + + The goal of this tutorial is to guide a new developer thru the + process of creating a set of &clsql; classes providing a + Object-Oriented interface to persistent data stored in an &sql; + database. We will assume that the reader is familiar with how + &sql; works, how relations (tables) should be structured, and + has created at least one &sql; application previously. We will + also assume a minor level of experience with Common Lisp. + + + + &clsql; provides two different interfaces to &sql; databases, a + Functional interface, and an Object-Oriented interface. The + Functional interface consists of a special syntax for embedded + &sql; expressions in Lisp, and provides lisp functions for &sql; + operations like SELECT and + UPDATE. The object-oriented interface provides + a way for mapping Common Lisp Objects System (CLOS) objects into + databases and includes functions for inserting new objects, + querying objects, and removing objects. Most applications will + use a combination of the two. + + + + &clsql; is based on the CommonSQL package from LispWorks Ltd, so the + documentation that LispWorks makes available online is useful for + &clsql; as well. It is suggested that developers new to &clsql; read + their documentation as well, as any differences between CommonSQL + and &clsql; are minor. LispWorks makes the following documents + available: + + + + + + + &lw; User Guide - The &commonsql; + Package + + + + + + + + + &lw; Reference Manual - The SQL + Package + + + + + + + + &commonsql; Tutorial by Nick Levine + + + + + + + + Data Modeling with &clsql; + + + Before we can create, query and manipulate &clsql; objects, we + need to define our data model as noted by Philip Greenspun + + + + Philip Greenspun's "SQL For Web Nerds" - Data + Modeling + + + + + + + When data modeling, you are telling the relational database + management system (RDBMS) the following: + + + + + What elements of the data you will store. + + + How large each element can be. + + + What kind of information each element can contain. + + + What elements may be left blank. + + + Which elements are constrained to a fixed range. + + + Whether and how various tables are to be linked. + + + + + With &sql; database one would do this by defining a set of + relations, or tables, followed by a set of queries for joining + the tables together in order to construct complex records. + However, with &clsql; we do this by defining a set of CLOS + classes, specifying how they will be turned into tables, and how + they can be joined to one another via relations between their + attributes. The &sql; tables, as well as the queries for + joining them together are created for us automatically, saving + us from dealing with some of the tedium of &sql;. + + + + Let us start with a simple example of two &sql; tables, and the + relations between them. + + + +CREATE TABLE EMPLOYEE ( emplid NOT NULL number(38), + first_name NOT NULL varchar2(30), + last_name NOT NULL varchar2(30), + email varchar2(100), + companyid NOT NULL number(38), + managerid number(38)) + +CREATE TABLE COMPANY ( companyid NOT NULL number(38), + name NOT NULL varchar2(100), + presidentid NOT NULL number(38)) + + + +This is of course the canonical &sql; tutorial example, "The Org Chart". + + + +In &clsql;, we would have two "view classes" (a fancy word for a class +mapped into a database). They would be defined as follows: + + + +(clsql:def-view-class employee () + ((emplid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :emplid) + (first-name + :accessor first-name + :type (string 30) + :initarg :first-name) + (last-name + :accessor last-name + :type (string 30) + :initarg :last-name) + (email + :accessor employee-email + :type (string 100) + :nulls-ok t + :initarg :email) + (companyid + :type integer + :initarg :companyid) + (managerid + :type integer + :nulls-ok t + :initarg :managerid)) + (:base-table employee)) + +(clsql:def-view-class company () + ((companyid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :companyid) + (name + :type (string 100) + :initarg :name) + (presidentid + :type integer + :initarg :presidentid)) + (:base-table company)) + + + + The DEF-VIEW-CLASS macro is just like the + normal CLOS DEFCLASS macro, except that it + handles several slot options that DEFCLASS + doesn't. These slot options have to do with the mapping of the slot + into the database. We only use a few of the slot options in the + above example, but there are several others. + + + + + + :column - The name of the &sql; column this slot is stored in. + Defaults to the slot name. If the slot name is not a valid &sql; + identifier, it is escaped, so foo-bar becomes foo_bar. + + + + + :db-kind - The kind of database mapping which + is performed for this slot. :base indicates + the slot maps to an ordinary column of the database view. + :key indicates that this slot corresponds to + part of the unique keys for this view, :join + indicates a join slot representing a relation to another view + and :virtual indicates that this slot is an ordinary CLOS slot. + Defaults to :base. + + + + :db-reader - If a string, then when reading + values from the database, the string will be used for a format + string, with the only value being the value from the database. + The resulting string will be used as the slot value. If a + function then it will take one argument, the value from the + database, and return the value that should be put into the slot. + + + + + :db-writer - If a string, then when reading + values from the slot for the database, the string will be used + for a format string, with the only value being the value of the + slot. The resulting string will be used as the column value in + the database. If a function then it will take one argument, the + value of the slot, and return the value that should be put into + the database. + + + + :db-type - A string which will be used as the + type specifier for this slots column definition in the database. + + + + + :void-value - The Lisp value to return if the + field is &null;. The default is &nil;. + + + + :db-info - A join specification. + + + + + In our example each table as a primary key attribute, which is + required to be unique. We indicate that a slot is part of the + primary key (&clsql; supports multi-field primary keys) by specifying + the :db-kind key slot option. + + + + The &sql; type of a slot when it is mapped into the database is + determined by the :type slot option. The argument + for the :type option is a Common Lisp datatype. + The &clsql; framework will determine the appropriate mapping + depending on the database system the table is being created in. If + we really wanted to determine what &sql; type was used for a slot, + we could specify a :db-type option like + "NUMBER(38)" and we would be guaranteed that the slot would be + stored in the database as a NUMBER(38). This is not recomended + because it could makes your view class unportable across database + systems. + + + + DEF-VIEW-CLASS also supports some class + options, like :base-table. The + :base-table option specifies what the table name + for the view class will be when it is mapped into the database. + + + + Another class option is :normalizedp, which signals + &clsql; to use a normalized schema for the mapping from slots to + &sql; columns. By default &clsql; includes all the slots of a parent + class that map to &sql; columns into the child class. This option + tells &clsql; to normalize the schema, so that a join is done on the + primary keys of the concerned tables to get a complete column set + for the classes. For more information, see + def-view-class. + + + + +Class Relations + + +In an &sql; only application, the EMPLOYEE and +COMPANY tables can be queried to determine things +like, "Who is Vladimir's manager?", "What company does Josef work +for?", and "What employees work for Widgets Inc.". This is done by +joining tables with an &sql; query. + + + +Who works for Widgets Inc.? + + + +SELECT first_name, last_name FROM employee, company + WHERE employee.companyid = company.companyid + AND company.company_name = "Widgets Inc." + + + +Who is Vladimir's manager? + + + +SELECT managerid FROM employee + WHERE employee.first_name = "Vladimir" + AND employee.last_name = "Lenin" + + + +What company does Josef work for? + + + +SELECT company_name FROM company, employee + WHERE employee.first_name = "Josef" + AND employee.last-name = "Stalin" + AND employee.companyid = company.companyid + + + +With &clsql; however we do not need to write out such queries because +our view classes can maintain the relations between employees and +companies, and employees to their managers for us. We can then access +these relations like we would any other attribute of an employee or +company object. In order to do this we define some join slots for our +view classes. + + + +What company does an employee work for? If we add the following slot +definition to the employee class we can then ask for it's +COMPANY slot and get the appropriate result. + + + + ;; In the employee slot list + (company + :accessor employee-company + :db-kind :join + :db-info (:join-class company + :home-key companyid + :foreign-key companyid + :set nil)) + + + +Who are the employees of a given company? And who is the president of +it? We add the following slot definition to the company view class and +we can then ask for it's EMPLOYEES slot and get the +right result. + + + + ;; In the company slot list + (employees + :reader company-employees + :db-kind :join + :db-info (:join-class employee + :home-key companyid + :foreign-key companyid + :set t)) + + (president + :reader president + :db-kind :join + :db-info (:join-class employee + :home-key presidentid + :foreign-key emplid + :set nil)) + + + +And lastly, to define the relation between an employee and their +manager: + + + + ;; In the employee slot list + (manager + :accessor employee-manager + :db-kind :join + :db-info (:join-class employee + :home-key managerid + :foreign-key emplid + :set nil)) + + + +&clsql; join slots can represent one-to-one, one-to-many, and +many-to-many relations. Above we only have one-to-one and one-to-many +relations, later we will explain how to model many-to-many relations. +First, let's go over the slot definitions and the available options. + + + +In order for a slot to be a join, we must specify that it's +:db-kind :join, as opposed to +:base or :key. Once we do that, we +still need to tell &clsql; how to create the join statements for the +relation. This is what the :db-info option does. It +is a list of keywords and values. The available keywords are: + + + + + + :join-class - The view class to which we want + to join. It can be another view class, or the same view class + as our object. + + + + :home-key - The slot(s) in the immediate object + whose value will be compared to the foreign-key slot(s) in the + join-class in order to join the two tables. It can be a single + slot-name, or it can be a list of slot names. + + + + :foreign-key - The slot(s) in the join-class + which will be compared to the value(s) of the home-key. + + + + + :set - A boolean which if false, indicates that + this is a one-to-one relation, only one object will be returned. + If true, than this is a one-to-many relation, a list of objects + will be returned when we ask for this slots value. + + + + +There are other :join-info options available in &clsql;, but we will +save those till we get to the many-to-many relation examples. + + + + Object Oriented Class Relations + + + &clsql; provides an Object Oriented Data Definition Language, which + provides a mapping from &sql; tables to CLOS objects. By default class + inheritance is handled by including all the columns from parent + classes into the child class. This means your database schema becomes + very much denormalized. The class option :normalizedp + can be used to disable the default behaviour and have &clsql; + normalize the database schemas of inherited classes. + + + + See def-view-class + for more information. + + + + + +Object Creation + + +Now that we have our model laid out, we should create some object. +Let us assume that we have a database connect set up already. We +first need to create our tables in the database: + + + +Note: the file examples/clsql-tutorial.lisp contains +view class definitions which you can load into your list at this point +in order to play along at home. + + + +(clsql:create-view-from-class 'employee) +(clsql:create-view-from-class 'company) + + + +Then we will create our objects. We create them just like you would +any other CLOS object: + + + +(defvar company1 (make-instance 'company + :companyid 1 + :presidentid 1 + :name "Widgets Inc.")) + +(defvar employee1 (make-instance 'employee + :emplid 1 + :first-name "Vladimir" + :last-name "Lenin" + :email "lenin@soviet.org" + :companyid 1)) + +(defvar employee2 (make-instance 'employee + :emplid 2 + :first-name "Josef" + :last-name "Stalin" + :email "stalin@soviet.org" + :companyid 1 + :managerid 1)) + + + +In order to insert an objects into the database we use the +UPDATE-RECORDS-FROM-INSTANCE function as follows: + + + +(clsql:update-records-from-instance employee1) +(clsql:update-records-from-instance employee2) +(clsql:update-records-from-instance company1) + + + + After you make any changes to an object, you have to specifically + tell &clsql; to update the &sql; database. The + UPDATE-RECORDS-FROM-INSTANCE method will write + all of the changes you have made to the object into the database. + + + + Since &clsql; objects are just normal CLOS objects, we can manipulate + their slots just like any other object. For instance, let's say + that Lenin changes his email because he was getting too much spam + from the German Socialists. + + + +;; Print Lenin's current email address, change it and save it to the +;; database. Get a new object representing Lenin from the database +;; and print the email + +;; This lets us use the functional &clsql; interface with [] syntax +(clsql:locally-enable-sql-reader-syntax) + +(format t "The email address of ~A ~A is ~A" + (first-name employee1) + (last-name employee1) + (employee-email employee1)) + +(setf (employee-email employee1) "lenin-nospam@soviets.org") + +;; Update the database +(clsql:update-records-from-instance employee1) + +(let ((new-lenin (car (clsql:select 'employee + :where [= [slot-value 'employee 'emplid] 1])))) + (format t "His new email is ~A" + (employee-email new-lenin))) + + + + Everything except for the last LET expression + is already familiar to us by now. To understand the call to + CLSQL:SELECT we need to discuss the + Functional &sql; interface and it's integration with the Object + Oriented interface of &clsql;. + + + + + +Finding Objects + + + Now that we have our objects in the database, how do we get them out + when we need to work with them? &clsql; provides a functional + interface to &sql;, which consists of a special Lisp reader macro + and some functions. The special syntax allows us to embed &sql; in + lisp expressions, and lisp expressions in &sql;, with ease. + + + + Once we have turned on the syntax with the expression: + + + +(clsql:locally-enable-sql-reader-syntax) + + + + We can start entering fragments of &sql; into our lisp reader. We + will get back objects which represent the lisp expressions. These + objects will later be compiled into &sql; expressions that are + optimized for the database backed we are connected to. This means + that we have a database independent &sql; syntax. Here are some + examples: + + + +;; an attribute or table name +[foo] => #<CLSQL-SYS::SQL-IDENT-ATTRIBUTE FOO> + +;; a attribute identifier with table qualifier +[foo bar] => #<CLSQL-SYS::SQL-IDENT-ATTRIBUTE FOO.BAR> + +;; a attribute identifier with table qualifier +[= "Lenin" [first_name]] => + #<CLSQL-SYS::SQL-RELATIONAL-EXP ('Lenin' = FIRST_NAME)> + +[< [emplid] 3] => + #<CLSQL-SYS::SQL-RELATIONAL-EXP (EMPLID < 3)> + +[and [< [emplid] 2] [= [first_name] "Lenin"]] => + #<CLSQL-SYS::SQL-RELATIONAL-EXP ((EMPLID < 2) AND + (FIRST_NAME = 'Lenin'))> + + +;; If we want to reference a slot in an object we can us the +;; SLOT-VALUE sql extension +[= [slot-value 'employee 'emplid] 1] => + #<CLSQL-SYS::SQL-RELATIONAL-EXP (EMPLOYEE.EMPLID = 1)> + +[= [slot-value 'employee 'emplid] + [slot-value 'company 'presidentid]] => + #<CLSQL-SYS::SQL-RELATIONAL-EXP (EMPLOYEE.EMPLID = COMPANY.PRESIDENTID)> + + + + The SLOT-VALUE operator is important because it + let's us query objects in a way that is robust to any changes in the + object->table mapping, like column name changes, or table name + changes. So when you are querying objects, be sure to use the + SLOT-VALUE &sql; extension. + + + + Since we can now formulate &sql; relational expression which can be + used as qualifiers, like we put after the WHERE + keyword in &sql; statements, we can start querying our objects. + &clsql; provides a function SELECT which can return + use complete objects from the database which conform to a qualifier, + can be sorted, and various other &sql; operations. + + + + The first argument to SELECT is a class name. it + also has a set of keyword arguments which are covered in the + documentation. For now we will concern ourselves only with the + :where keyword. Select returns a list of objects, or nil if it + can't find any. It's important to remember that it always returns a + list, so even if you are expecting only one result, you should + remember to extract it from the list you get from + SELECT. + + + +;; all employees +(clsql:select 'employee) +;; all companies +(clsql:select 'company) + +;; employees named Lenin +(clsql:select 'employee :where [= [slot-value 'employee 'last-name] + "Lenin"]) + +(clsql:select 'company :where [= [slot-value 'company 'name] + "Widgets Inc."]) + +;; Employees of Widget's Inc. +(clsql:select 'employee + :where [and [= [slot-value 'employee 'companyid] + [slot-value 'company 'companyid]] + [= [slot-value 'company 'name] + "Widgets Inc."]]) + +;; Same thing, except that we are using the employee +;; relation in the company view class to do the join for us, +;; saving us the work of writing out the &sql;! +(company-employees company1) + +;; President of Widgets Inc. +(president company1) + +;; Manager of Josef Stalin +(employee-manager employee2) + + + + + +Deleting Objects + + + Now that we know how to create objects in our database, manipulate + them and query them (including using our predefined relations to + save us the trouble writing alot of &sql;) we should learn how to + clean up after ourself. It's quite simple really. The function + DELETE-INSTANCE-RECORDS will remove an object + from the database. However, when we remove an object we are + responsible for making sure that the database is left in a correct + state. + + + + For example, if we remove a company record, we need to either remove + all of it's employees or we need to move them to another company. + Likewise if we remove an employee, we should make sure to update any + other employees who had them as a manager. + + + + + +Conclusion + + + There are many nooks and crannies to &clsql;, some of which are + covered in the Xanalys documents we refered to earlier, some are + not. The best documentation at this time is still the source code + for &clsql; itself and the inline documentation for its various + functions. + + + + + diff --git a/doc/entities.inc b/doc/entities.inc new file mode 100644 index 0000000..25ef73e --- /dev/null +++ b/doc/entities.inc @@ -0,0 +1,31 @@ +ASDF"> +CLOCC"> +CCLAN"> +UFFI"> +FFI"> +CLSQL"> +MaiSQL"> +SQL"> +UncommonSQL"> +CommonSQL"> +MySQL"> +PostgreSQL"> +SQLite version 2"> +SQLite version 3"> +Oracle"> +ODBC"> +AODBC"> +CMUCL"> +SCL"> +MD5"> +SBCL"> +OpenMCL"> +Lispworks"> +AllegroCL"> +ANSI Common Lisp"> +T"> +NIL"> +NULL"> +C"> +UNIX"> +Microsoft Windows"> diff --git a/doc/fo.xsl b/doc/fo.xsl new file mode 100644 index 0000000..5cb69da --- /dev/null +++ b/doc/fo.xsl @@ -0,0 +1,6 @@ + + + + + diff --git a/doc/global-index.xml b/doc/global-index.xml new file mode 100644 index 0000000..064cf53 --- /dev/null +++ b/doc/global-index.xml @@ -0,0 +1,137 @@ + + +%myents; +]> + + + Index + + + + Alphabetical Index for package CLSQL + Clickable index of all symbols + + + + + + + + + + *BACKEND-WARNING-BEHAVIOR* + *CACHE-TABLE-QUERIES-DEFAULT* + *CONNECT-IF-EXISTS* + *DB-AUTO-SYNC* + *DEFAULT-DATABASE* + *DEFAULT-DATABASE-TYPE* + *DEFAULT-UPDATE-OBJECTS-MAX-LEN* + *DEFAULT-STRING-LENGTH* + *INITIALIZED-DATABASE-TYPES* + + + + ADD-SQL-STREAM + ADD-TRANSACTION-COMMIT-HOOK + ADD-TRANSACTION-ROLLBACK-HOOK + ATTRIBUTE-TYPE + CACHE-TABLE-QUERIES + COMMIT + CONNECT + CONNECTED-DATABASES + CREATE-DATABASE + CREATE-INDEX + CREATE-SEQUENCE + CREATE-TABLE + CREATE-VIEW + CREATE-VIEW-FROM-CLASS + DATABASE + DATABASE-NAME + DATABASE-NAME-FROM-SPEC + DATABASE-TYPE + DEF-VIEW-CLASS + DELETE-INSTANCE-RECORDS + DELETE-RECORDS + DELETE-SQL-STREAM + DESTROY-DATABASE + DISABLE-SQL-READER-SYNTAX + DISCONNECT + DISCONNECT-POOLED + DO-QUERY + DROP-INDEX + DROP-SEQUENCE + DROP-TABLE + DROP-VIEW + DROP-VIEW-FROM-CLASS + ENABLE-SQL-READER-SYNTAX + EXECUTE-COMMAND + FIND-DATABASE + IN-TRANSACTION-P + INDEX-EXISTS-P + INITIALIZE-DATABASE-TYPE + INSERT-RECORDS + INSTANCE-REFRESHED + LIST-ATTRIBUTE-TYPES + LIST-ATTRIBUTES + LIST-CLASSES + LIST-DATABASES + LIST-INDEXES + LIST-SEQUENCES + LIST-SQL-STREAMS + LIST-TABLES + LIST-VIEWS + LOCALLY-DISABLE-SQL-READER-SYNTAX + LOCALLY-ENABLE-SQL-READER-SYNTAX + LOOP-FOR-AS-TUPLES + MAP-QUERY + PROBE-DATABASE + QUERY + RECONNECT + RESTORE-SQL-READER-SYNTAX-STATE + ROLLBACK + SELECT + SEQUENCE-EXISTS-P + SEQUENCE-LAST + SEQUENCE-NEXT + SET-AUTOCOMMIT + SET-SEQUENCE-POSITION + SQL + SQL-CONDITION + SQL-CONNECTION-ERROR + SQL-DATABASE-DATA-ERROR + SQL-DATABASE-ERROR + SQL-DATABASE-WARNING + SQL-ERROR + SQL-EXPRESSION + SQL-FATAL-ERROR + SQL-OPERATION + SQL-OPERATOR + SQL-RECORDING-P + SQL-STREAM + SQL-TEMPORARY-ERROR + SQL-TIMEOUT-ERROR + SQL-USER-ERROR + SQL-WARNING + START-SQL-RECORDING + START-TRANSACTION + STATUS + STOP-SQL-RECORDING + TABLE-EXISTS-P + TRUNCATE-DATABASE + UPDATE-INSTANCE-FROM-RECORDS + UPDATE-OBJECTS-JOINS + UPDATE-RECORD-FROM-SLOT + UPDATE-RECORD-FROM-SLOTS + UPDATE-RECORDS + UPDATE-RECORDS-FROM-INSTANCE + UPDATE-SLOT-FROM-RECORD + VIEW-EXISTS-P + WITH-DATABASE + WITH-DEFAULT-DATABASE + WITH-TRANSACTION + + + + diff --git a/doc/glossary.xml b/doc/glossary.xml new file mode 100644 index 0000000..fd73384 --- /dev/null +++ b/doc/glossary.xml @@ -0,0 +1,197 @@ + + +%myents; +]> + + + + This glossary is still very thinly populated, and not all + references in the main text have been properly linked and + coordinated with this glossary. This will hopefully change in + future revisions. + + + + Attribute + + + A property of objects stored in a database table. Attributes are + represented as columns (or fields) in a table. + + + + + Active database + + + + Connection + + + + + Column + + + + Data Definition Language (DDL) + + + + The subset of SQL used for defining and examining the + structure of a database. + + + + + Data Manipulation Language (DML) + + + + The subset of SQL used for inserting, deleting, updating and + fetching data in a database. + + + + + database + + + + Database Object + + An object of type database. + + + + Field + + + + Field Types Specifier + + A value that specifies the type of each field in a query. + + + + Foreign Function Interface + (FFI) + + + + An interface from Common Lisp to a external library which + contains compiled functions written in other programming + languages, typically C. + + + + + Query + + + An SQL statement which returns a set of results. + + + + + RDBMS + + + A Relational DataBase Management System + (RDBMS) is a software package for managing + a database in which the data is defined, organised and + accessed as rows and columns of a table. + + + + + Record + + + A sequence of attribute values stored in a database table. + + + + + Row + + + + Structured Query Language + (SQL) + + + + An ANSI standard language for storing and retrieving data + in a relational database. + + + + + SQL Expression + + Either a string containing a valid SQL statement, or + an object of type sql-expression. + + + + + + Table + + + A collection of data which is defined, stored and accessed as + tuples of attribute values (i.e., rows and columns). + + + + + Transaction + + + An atomic unit of one or more SQL statements of which all or none are + successfully executed. + + + + + Tuple + + + + View + + + A table display whose structure and content are derived from an + existing table via a query. + + + + + View Class + + + The class standard-db-object or one of + its subclasses. + + + + + diff --git a/doc/html.tar.gz b/doc/html.tar.gz new file mode 100644 index 0000000..8718c23 Binary files /dev/null and b/doc/html.tar.gz differ diff --git a/doc/html.xsl b/doc/html.xsl new file mode 100644 index 0000000..163d76b --- /dev/null +++ b/doc/html.xsl @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/doc/html_chunk.xsl b/doc/html_chunk.xsl new file mode 100644 index 0000000..a2bb88f --- /dev/null +++ b/doc/html_chunk.xsl @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/doc/intro.xml b/doc/intro.xml new file mode 100644 index 0000000..936cf59 --- /dev/null +++ b/doc/intro.xml @@ -0,0 +1,265 @@ + + +%myents; +]> + + + Introduction + + + Purpose + &clsql; is a Common Lisp interface to SQL databases. A number of Common + Lisp implementations and SQL databases are supported. The general + structure of &clsql; is based on the &commonsql; package by + LispWorks Ltd. + + + + + History + + The &clsql; project was started by Kevin M. Rosenberg in 2001 to + support SQL access on multiple Common Lisp implementations using + the &uffi; library. The initial code was based substantially on + Pierre R. Mai's excellent &maisql; package. In late 2003, the + &usql; library was orphaned by its author, onShore Development, + Inc. In April 2004, Marcus Pearce ported the &usql; library to + &clsql;. The &usql; library provides a &commonsql;-compatible + API for &clsql;. + + + The main changes from &maisql; and &usql; are: + + + Port from the &cmucl; FFI to &uffi; which provide + compatibility with the major Common Lisp + implementations. + + + Optimized loading of integer and floating-point fields. + + + Additional database backends: &odbc;, &aodbc;, &sqlite; + and &sqlite3;. + + + A compatibility layer for &cmucl; specific code. + + + Much improved robustness for the &mysql; back-end + along with version 4 client library support. + + + Improved library loading and installation documentation. + + + Improved packages and symbol export. + + + Pooled connections. + + + Integrated transaction support for the classic + &maisql; iteration macros. + + + + + + + Prerequisites + + + &asdf; + + &clsql; uses &asdf; to compile and load its components. + &asdf; is included in the &cclan; + collection. + + + + + &uffi; + + &clsql; uses &uffi; + as a Foreign Function Interface + (FFI) to support + multiple &cl; implementations. + + + + + &md5; + &clsql;'s postgresql-socket interface uses Pierre Mai's + md5 + module. + + + + Supported Common Lisp Implementation + + The implementations that support &clsql; is governed by the supported + implementations of &uffi;. The following implementations are supported: + + + &acl; v6.2 through 8.0 on Debian Linux x86 & + x86_64 & PowerPC, FreeBSD 4.5, and Microsoft Windows + XP. + &lw; v4.3 and v4.4 on Debian Linux and Microsoft + Windows XP. + &cmucl; 18e on Debian Linux, FreeBSD 4.5, and + Solaris 2.8. 19c on Debian Linux. + &sbcl; 0.8.4 through 0.9.16 on Debian + Linux. + &scl; 1.1.1 on Debian Linux. + &openmcl; 0.14 PowerPC and 1.0pre AMD64 on Debian Linux . + + + + + Supported &sql; Implementation + + &clsql; supports the following databases: + + + &mysql; (tested v3.23.51, v4.0.18, 5.0.24). + &postgresql; (tested with v7.4 and 8.0 with both direct API and TCP + socket connections. + &sqlite;. + &sqlite3;. + Direct &odbc; interface. + &oracle; OCI. + Allegro's DB interface (&aodbc;). + + + + + + + Installation + + + Ensure &asdf; is loaded + + Simply load the file asdf.lisp. + +(load "asdf.lisp") + + + + + + Build &c; helper libraries + &clsql; uses functions that require 64-bit integer + parameters and return values. The &ffi; in most &clsql; + implementations do not support 64-bit integers. Thus, C helper + libraries are required to break these 64-bit integers into two compatible + 32-bit integers. The helper libraries reside in the directories + uffi and db-mysql. + + + + &mswindows; + + Files named Makefile.msvc are supplied + for building the libraries under Microsoft Windows. Since + &mswindows; does not come with that compiler, compiled + DLL and LIB library files are + supplied with &clsql;. + + + + + &unix; + + Files named Makefile are supplied for + building the libraries under &unix;. Loading the + .asd files automatically invokes + make when necessary. So, manual + building of the helper libraries is not necessary on most + &unix; systems. However, the location of the &mysql; library + files and include files may need to adjusted in + db-mysql/Makefile on non-Debian + systems. + + + + + + + Add &uffi; path + + Unzip or untar the &uffi; distribution which creates a directory + for the &uffi; files. Add that directory to &asdf;'s asdf:*central-registry*. + You can do that by pushing the pathname of the directory onto this variable. + The following example code assumes the &uffi; files reside in the + /usr/share/lisp/uffi/ + directory. + +(push #P"/usr/share/lisp/uffi/" asdf:*central-registry*) + + + + + Add &md5; path + + If you plan to use the clsql-postgresql-socket interface, you + must load the md5 module. Unzip or untar the cl-md5 + distribution, which creates a directory for the cl-md5 files. + Add that directory to &asdf;'s + asdf:*central-registry*. You can do that by + pushing the pathname of the directory onto this variable. The + following example code assumes the cl-md5 files reside in the + /usr/share/lisp/cl-md5/ directory. + +(push #P"/usr/share/lisp/cl-md5/" asdf:*central-registry*) + + + + + + Add &clsql; path and load module + + Unzip or untar the &clsql; distribution which creates a + directory for the &clsql; files. Add that directory to &asdf;'s + asdf:*central-registry*. You can do that by + pushing the pathname of the directory onto this variable. The + following example code assumes the &clsql; files reside in the + /usr/share/lisp/clsql/ directory. You need + to load the clsql system. + + +(push #P"/usr/share/lisp/clsql/" asdf:*central-registry*) +(asdf:operate 'asdf:load-op 'clsql) ; main CLSQL package + + + + + + Run test suite (optional) + + The test suite can be executed using the &asdf; + test-op operator. If &clsql; has not been + loaded with asdf:load-op, the + asdf:test-op operator will automatically load + &clsql;. A configuration file named + .clsql-test.config must be created in + your home directory. There are instructures on the format of + that file in the tests/README. After + creating .clsql-test.config, you can run + the test suite with &asdf;: + + (asdf:operate 'asdf:test-op 'clsql) + + + + + + + + diff --git a/doc/mysql-macosx-notes.txt b/doc/mysql-macosx-notes.txt new file mode 100644 index 0000000..9687f1d --- /dev/null +++ b/doc/mysql-macosx-notes.txt @@ -0,0 +1,60 @@ +Instructions: + +---------------- +1. Install MySQL from Fink, not from mysql.com + +It is sufficient to install the binaries from Fink. +CLSQL requires the MySQL library libmysqlclient.dylib; this library +is not provided with the mysql binary install. +This library is in /sw/lib/mysql . However, it is actually called +libmysqlclient.14.0.0.dylib . +----------------- +2. After installing MySQL, do the following at the shell. +(Assuming that you do not want to have to load CLSQL as root user +every time you use it.) + +2a. Create a symbolic link from libmysqlclient.14.0.0.dylib to the +name libmysqlclient.dylib . +2b. Change ownership to yourself along the path /sw/lib/mysql/ +libmysqlclient.dylib . +----------------- +3. Either install CocoaMySQL or download the source of MySQL (e.g. +5.0) from mysql.com. + +CLSQL requires MySQL's header files. These files are not provided +with Fink's binary MySQL install. +In CocoaMySQL the headers are found at: /Applications/CocoaMySQL.app/ +Contents/Frameworks/SMySQL.framework/Versions/A/Headers +In MySQL 5.0 sources, the headers are found at: ~/Desktop/ +mysql-5.0.15/include + +3a. Copy the directory full of headers to /sw/include/mysql +3b. Make yourself the owner of these files. + +You may now dispose of CocoaMySQL or the MySQL sources, if you desire. +----------------- +4. In the CLSQL sources, modify db-mysql/makefile to read as follows: + +... CFLAGS="-I /sw/include/mysql" LDFLAGS=" -L/sw/lib/mysql/ ... + +----------------- +5. In Lisp, do the following: + +Assuming asdf and the CLSQL & UFFI sources are in the same directory; +substitute the appropriate path for ~ . +The code below is right out of the CLSQL docs, but note particularly +the commented expression. + +(load "~/asdf.lisp") + +(progn + (push "~/uffi-1.5.5/" asdf:*central-registry*) + (asdf:operate 'asdf:load-op :uffi) + (push "~/clsql-3.3.4/" asdf:*central-registry*) + (asdf:operate 'asdf:load-op :clsql)) + +(progn + (in-package :clsql) + (setf *default-database-type* :mysql) + (clsql:push-library-path "/sw/lib/mysql/") ; !! + (asdf:operate 'asdf:load-op 'clsql-mysql)) diff --git a/doc/preface.xml b/doc/preface.xml new file mode 100644 index 0000000..5e4aede --- /dev/null +++ b/doc/preface.xml @@ -0,0 +1,17 @@ + + +%myents; +]> + + + Preface + + This guide provides reference to the features of &clsql;. The + first chapter provides an introduction to &clsql; and installation + instructions. The reference sections document all user accessible + symbols with examples of usage. There is a glossary of commonly + used terms with their definitions. + + diff --git a/doc/ref-clsql-sys.xml b/doc/ref-clsql-sys.xml new file mode 100644 index 0000000..56d1615 --- /dev/null +++ b/doc/ref-clsql-sys.xml @@ -0,0 +1,103 @@ + + +%myents; +]> + + + <symbol>CLSQL-SYS</symbol> + + This part gives a reference to the symbols exported from + the package CLSQL-SYS, which are not exported + from CLSQL package.. These symbols are part of + the interface for database back-ends, but not part of the normal + user-interface of &clsql;. + + + + DATABASE-INITIALIZE-DATABASE-TYPE + Back-end part of initialize-database-type. + Generic Function + + + Syntax + database-initialize-database-type database-type => result + + + Arguments and Values + + + database-type + + A keyword indicating the database type to + initialize. + + + + result + + Either t if the initialization + succeeds or nil if it fails. + + + + + + Description + This generic function implements the main part of the + database type initialization performed by + initialize-database-type. After + initialize-database-type has checked + that the given database type has not been initialized + before, as indicated by + *initialized-database-types*, it will call + this function with the database type as it's sole + parameter. Database back-ends are required to define a + method on this generic function which is specialized via an + eql-specializer to the keyword representing their database + type. + Database back-ends shall indicate successful + initialization by returning t from their + method, and nil otherwise. Methods for + this generic function are allowed to signal errors of type + clsql-error or subtypes thereof. + They may also signal other types of conditions, if + appropriate, but have to document this. + + + Examples + + + + Side Effects + All necessary side effects to initialize the database + instance. + + + Affected By + None. + + + Exceptional Situations + Conditions of type clsql-error + or other conditions may be signalled, depending on the + database back-end. + + + See Also + + + initialize-database-type + *initialized-database-types* + + + + + Notes + None. + + + diff --git a/doc/ref-clsql.xml b/doc/ref-clsql.xml new file mode 100644 index 0000000..b44ac4d --- /dev/null +++ b/doc/ref-clsql.xml @@ -0,0 +1,2416 @@ + + +%myents; +]> + + + <symbol>CLSQL</symbol> + + This part gives a reference to the symbols exported from the + CLSQL package. These symbols constitute + the normal user-interface of &clsql;. Currently, the symbols of + the &commonsql;-API are not documented here. + + + + + SQL-CONDITION + the super-type of all + &clsql;-specific + conditions + Condition Type + + + Class Precedence List + + + sql-condition + condition + t + + + + + Description + This is the super-type of all + &clsql;-specific conditions + defined by &clsql;, or any of it's + database-specific interfaces. There are no defined + initialization arguments nor any accessors. + + + + + SQL-ERROR + the super-type of all + &clsql;-specific + errors + Condition Type + + + Class Precedence List + + + sql-error + error + serious-condition + sql-condition + condition + t + + + + + Description + This is the super-type of all + &clsql;-specific conditions that + represent errors, as defined by + &clsql;, or any of it's + database-specific interfaces. There are no defined + initialization arguments nor any accessors. + + + + + SQL-WARNING + the super-type of all + &clsql;-specific + warnings + Condition Type + + + Class Precedence List + + + sql-warning + warning + sql-condition + condition + t + + + + + Description + This is the super-type of all + &clsql;-specific conditions that + represent warnings, as defined by + &clsql;, or any of it's + database-specific interfaces. There are no defined + initialization arguments nor any accessors. + + + + + + CLSQL-USER-ERROR + condition representing errors because of invalid + parameters from the library user. + Condition Type + + + Class Precedence List + + + sql-error + sql-condition + condition + t + + + + + Description + This condition represents errors that occur because the + user supplies invalid data to &clsql;. This includes errors such as + an invalid format connection specification or an error in the syntax + for the LOOP macro extensions. + + + + + SQL-CONNECTION-ERROR + condition representing errors during + connection + Condition Type + + + Class Precedence List + + + sql-connection-error + sql-database-error + sql-error + sql-condition + condition + t + + + + + Description + This condition represents errors that occur while trying + to connect to a database. The following initialization + arguments and accessors exist: + + Initarg + Accessor + Description + + :database-type + sql-connection-error-database-type + Database type for the connection attempt + + + :connection-spec + sql-connection-error-connection-spec + The connection specification used in the + connection attempt. + + + :errno + sql-connection-error-errno + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :error + sql-connection-error-error + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + + + SQL-DATABASE-ERROR + condition representing errors during query or + command execution + Condition Type + + + Class Precedence List + + + sql-database-error + sql-error + error + serious-condition + sql-condition + condition + t + + + + + Description + This condition represents errors that occur while + executing SQL statements, either as part of query operations + or command execution, either explicitly or implicitly, as + caused e.g. by with-transaction. + The following initialization arguments and accessors exist: + + Initarg + Accessor + Description + + :database + sql-database-error-database + The database object that was involved in the + incident. + + + :expression + sql-database-error-expression + The SQL expression whose execution caused the error. + + + :errno + sql-database-error-errno + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :error + sql-database-error-error + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + + + + + *DEFAULT-DATABASE-TYPE* + The default database type to use + Variable + + + Value Type + Any keyword representing a valid database back-end of + &clsql;, or + nil. + + + Initial Value + nil + + + Description + The value of this variable is used in calls to + initialize-database-type and + connect as the default + value of the database-type + parameter. + + If the value of this variable is nil, + then all calls to + initialize-database-type or + connect will have to specify the + database-type to use, or a + general-purpose error will be signalled. + + + + Examples + +(setf *default-database-type* :mysql) +=> :mysql +(initialize-database-type) +=> t + + + + Affected By + None. + + + See Also + None. + + + Notes + None. + + + + + *INITIALIZED-DATABASE-TYPES* + List of all initialized database types + Variable + + + Value Type + A list of all initialized database types, each of which + represented by it's corresponding keyword. + + + Initial Value + nil + + + Description + This variable is updated whenever + initialize-database-type is called for a + database type which hasn't already been initialized before, + as determined by this variable. In that case the keyword + representing the database type is pushed onto the list + stored in + *INITIALIZED-DATABASE-TYPES*. + + Attempts to modify the value of this variable will + result in undefined behaviour. + + + + Examples + +(setf *default-database-type* :mysql) +=> :mysql +(initialize-database-type) +=> t +*initialized-database-types* +=> (:MYSQL) + + + + Affected By + + + initialize-database-type + + + + + See Also + None. + + + Notes + Direct access to this variable is primarily provided + because of compatibility with Harlequin's Common + SQL. + + + + + INITIALIZE-DATABASE-TYPE + Initializes a database type + Function + + + Syntax + initialize-database-type &key database-type => result + + + Arguments and Values + + + database-type + + The database type to initialize, i.e. a keyword + symbol denoting a known database back-end. Defaults to + the value of + *default-database-type*. + + + + result + + Either nil if the initialization + attempt fails, or t otherwise. + + + + + + Description + If the back-end specified by + database-type has not already been + initialized, as seen from + *initialized-database-types*, an attempt is + made to initialize the database. If this attempt succeeds, + or the back-end has already been initialized, the function + returns t, and places the keyword denoting the database type + onto the list stored in + *initialized-database-types*, if not + already present. + If initialization fails, the function returns + nil, and/or signals an error of type + clsql-error. The kind of action + taken depends on the back-end and the cause of the + problem. + + + Examples + +*initialized-database-types* +=> NIL +(setf *default-database-type* :mysql) +=> :MYSQL +(initialize-database-type) +>> Compiling LAMBDA (#:G897 #:G898 #:G901 #:G902): +>> Compiling Top-Level Form: +>> +=> T +*initialized-database-types* +=> (:MYSQL) +(initialize-database-type) +=> T +*initialized-database-types* +=> (:MYSQL) + + + + Side Effects + The database back-end corresponding to the database type + specified is initialized, unless it has already been + initialized. This can involve any number of other side + effects, as determined by the back-end implementation (like + e.g. loading of foreign code, calling of foreign code, + networking operations, etc.). If initialization is + attempted and succeeds, the + database-type is pushed onto the list + stored in + *initialized-database-types*. + + + Affected by + + + *default-database-type* + *initialized-database-types* + + + + + Exceptional Situations + If an error is encountered during the initialization + attempt, the back-end may signal errors of kind + clsql-error. + + + See Also + None. + + + Notes + None. + + + + + + *CONNECT-IF-EXISTS* + Default value for the + if-exists parameter of + connect. + Variable + + + Value Type + A valid argument to the if-exists + parameter of connect, i.e. one of + + :new + :warn-new + :error + :warn-old + :old + . + + + + Initial Value + :error + + + Description + The value of this variable is used in calls to + connect as the default + value of the if-exists + parameter. See connect for + the semantics of the valid values for this variable. + + + Examples + None. + + + Affected By + None. + + + See Also + + + connect + + + + + Notes + None. + + + + + CONNECTED-DATABASES + Return the list of active database + objects. + Function + + + Syntax + connected-databases => databases + + + Arguments and Values + + + databases + + The list of active database objects. + + + + + + Description + This function returns the list of active database + objects, i.e. all those database objects created by calls to + connect, which have not been closed by + calling disconnect on them. + + The consequences of modifying the list returned by + connected-databases are + undefined. + + + + Examples + +(connected-databases) +=> NIL +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}> +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {4830C5AD}> +(connected-databases) +=> (#<CLSQL-MYSQL:MYSQL-DATABASE {4830C5AD}> + #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}>) +(disconnect) +=> T +(connected-databases) +=> (#<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}>) +(disconnect) +=> T +(connected-databases) +=> NIL + + + + Side Effects + None. + + + Affected By + + + connect + disconnect + + + + + Exceptional Situations + None. + + + See Also + None. + + + Notes + None. + + + + + *DEFAULT-DATABASE* + The default database object to use + Variable + + + Value Type + Any object of type database, or nil to + indicate no default database. + + + Initial Value + nil + + + Description + Any function or macro in + &clsql; that operates on a + database uses the value of this variable as the default + value for it's database + parameter. + The value of this parameter is changed by calls to + connect, which sets + *default-database* to the database object + it returns. It is also changed by calls to + disconnect, when the database object + being disconnected is the same as the value of + *default-database*. In this case + disconnect sets + *default-database* to the first database + that remains in the list of active databases as returned by + connected-databases, or + nil if no further active databases + exist. + The user may change *default-database* + at any time to a valid value of his choice. + + If the value of *default-database* is + nil, then all calls to + &clsql; functions on databases + must provide a suitable database + parameter, or an error will be signalled. + + + + Examples + +(connected-databases) +=> NIL +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48385F55}> +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {483868FD}> +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql :if-exists :new) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48387265}> +*default-database* +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48387265}> +(disconnect) +=> T +*default-database* +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {483868FD}> +(disconnect) +=> T +*default-database* +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48385F55}> +(disconnect) +=> T +*default-database* +=> NIL +(connected-databases) +=> NIL + + + + Affected By + + + connect + disconnect + + + + + See Also + + + connected-databases + + + + + Notes + + This variable is intended to facilitate working with + &clsql; in an interactive + fashion at the top-level loop, and because of this, + connect and + disconnect provide some fairly + complex behaviour to keep + *default-database* set to useful values. + Programmatic use of &clsql; + should never depend on the value of + *default-database* and should provide + correct database objects via the + database parameter to functions + called. + + + + + + + DATABASE + The super-type of all + &clsql; databases + Class + + + Class Precedence List + + + database + standard-object + t + + + + + Description + This class is the superclass of all + &clsql; databases. The different + database back-ends derive subclasses of this class to + implement their databases. No instances of this class are + ever created by &clsql;. + + + + + CLOSED-DATABASE + The class representing all closed + &clsql; databases + Class + + + Class Precedence List + + + closed-database + standard-object + t + + + + + Description + &clsql; database + instances are changed to this class via + change-class after they are closed via + disconnect. All functions and generic + functions that take database objects as arguments will + signal errors of type + clsql-closed-error when they are + called on instances of closed-database, with + the exception of database-name, which + will continue to work as for instances of + database. + + + + + + DATABASE-NAME + Get the name of a database object + Generic Function + + + Syntax + database-name database => name + + + Arguments and Values + + + database + + A database object, either of type + database or of type + closed-database. + + + + name + + A string describing the identity of the database + to which this database object is connected to. + + + + + + Description + This function returns the database name of the given + database. The database name is a string which somehow + describes the identity of the database to which this + database object is or has been connected. The database name + of a database object is determined at + connect time, when a call to + database-name-from-spec derives the + database name from the connection specification passed to + connect in the + connection-spec parameter. + The database name is used via + find-database in + connect to determine whether database + connections to the specified database exist already. + Usually the database name string will include + indications of the host, database name, user, or port that + where used during the connection attempt. The only + important thing is that this string shall try to identify + the database at the other end of the connection. Connection + specifications parts like passwords and credentials shall + not be used as part of the database name. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + + + + Side Effects + None. + + + Affected By + + + database-name-from-spec + + + + + Exceptional Situations + Will signal an error if the object passed as the + database parameter is neither of type + database nor of type + closed-database. + + + See Also + + + connect + find-database + + + + + Notes + None. + + + + + FIND-DATABASE + Locate a database object through it's + name. + Function + + + Syntax + find-database database &optional errorp => result + + + Arguments and Values + + + database + + A database object or a string, denoting a database + name. + + + + errorp + + A generalized boolean. Defaults to + t. + + + + result + + Either a database object, or, if + errorp is nil, + possibly nil. + + + + + + Description + find-database locates an active + database object given the specification in + database. If + database is an object of type + database, find-database + returns this. Otherwise it will search the active databases + as indicated by the list returned by + connected-databases for a database + whose name (as returned by + database-name is equal as per + string= to the string passed as + database. If it succeeds, it returns + the first database found. + If it fails to find a matching database, it will signal + an error of type clsql-error if + errorp is true. If + errorp is nil, it + will return nil instead. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + +(find-database "dent/newesim/dent") +=> #<CLSQL-MYSQL:MYSQL-DATABASE {484E91C5}> +(find-database "/template1/dent") +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(find-database "www.pmsf.de/template1/dent" nil) +=> NIL +(find-database **) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> + + + + Side Effects + None. + + + Affected By + + + connected-databases + + + + + Exceptional Situations + Will signal an error of type + clsql-error if no matching database + can be found, and errorp is true. + Will signal an error if the value of + database is neither an object of type + database nor a string. + + + See Also + + + database-name + database-name-from-spec + + + + + Notes + None. + + + + + + CONNECT + create a connection to a database + Function + + + Syntax + connect connection-spec &key if-exists database-type pool => database + + + Arguments and Values + + + connection-spec + + A connection specification + + + + if-exists + + This indicates the action to take if a connection + to the same database exists already. See below for the + legal values and actions. It defaults to the value of + *connect-if-exists*. + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + pool + + A boolean flag. If &t;, acquire connection from a + pool of open connections. If the pool is empty, a new + connection is created. The default is &nil;. + + + + + database + + The database object representing the connection. + + + + + + Description + This function takes a connection specification and + a database type and creates a connection to the database + specified by those. The type and structure of the + connection specification depend on the database type. + The parameter if-exists specifies + what to do if a connection to the database specified exists + already, which is checked by calling + find-database on the database name + returned by database-name-from-spec + when called with the connection-spec + and database-type parameters. The + possible values of if-exists are: + + + :new + + Go ahead and create a new connection. + + + + :warn-new + + This is just like :new, but + also signals a warning of type + clsql-exists-warning, + indicating the old and newly created + databases. + + + + :error + + This will cause connect to + signal a correctable error of type + clsql-exists-error. The + user may choose to proceed, either by indicating + that a new connection shall be created, via the + restart create-new, or by + indicating that the existing connection shall be + used, via the restart + use-old. + + + + :old + + This will cause connect to + use an old connection if one exists. + + + + :warn-old + + This is just like :old, but + also signals a warning of type + clsql-exists-warning, + indicating the old database used, via the slots + old-db and + new-db + + + + + The database name of the returned database object will + be the same under string= as that which + would be returned by a call to + database-name-from-spec with the given + connection-spec and + database-type parameters. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48036F6D}> +(database-name *) +=> "dent/newesim/dent" + +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +>> In call to CONNECT: +>> There is an existing connection #<CLSQL-MYSQL:MYSQL-DATABASE {48036F6D}> to database dent/newesim/dent. +>> +>> Restarts: +>> 0: [CREATE-NEW] Create a new connection. +>> 1: [USE-OLD ] Use the existing connection. +>> 2: [ABORT ] Return to Top-Level. +>> +>> Debug (type H for help) +>> +>> (CONNECT ("dent" "newesim" "dent" "dent") :IF-EXISTS NIL :DATABASE-TYPE ...) +>> Source: +>> ; File: /prj/CLSQL/sql/sql.cl +>> (RESTART-CASE (ERROR 'CLSQL-EXISTS-ERROR :OLD-DB OLD-DB) +>> (CREATE-NEW NIL :REPORT "Create a new connection." +>> (SETQ RESULT #)) +>> (USE-OLD NIL :REPORT "Use the existing connection." +>> (SETQ RESULT OLD-DB))) +>> 0] 0 +=> #<CLSQL-MYSQL:MYSQL-DATABASE {480451F5}> + + + + Side Effects + A database connection is established, and the resultant + database object is registered, so as to appear in the list + returned by connected-databases. + + + Affected by + + + *default-database-type* + *connect-if-exists* + + + + + Exceptional Situations + If the connection specification is not syntactically or + semantically correct for the given database type, an error + of type clsql-invalid-spec-error is + signalled. If during the connection attempt an error is + detected (e.g. because of permission problems, network + trouble or any other cause), an error of type + sql-connection-error is + signalled. + If a connection to the database specified by + connection-spec exists already, + conditions are signalled according to the + if-exists parameter, as described + above. + + + See Also + + + connected-databases + disconnect + + + + + Notes + None. + + + + + + DISCONNECT + close a database connection + Function + + + Syntax + disconnect &key database pool => t + + + Arguments and Values + + + pool + + A boolean flag indicating whether to put the database into a +pool of opened databases. If &t;, rather than terminating the database connection, the +connection is left open and the connection is placed into a pool of connections. Subsequent +calls to connect can then reuse this connection. +The default is &nil;. + + + + + database + + The database to disconnect, which defaults to the + database indicated by + *default-database*. + + + + + + Description + This function takes a database object as + returned by connect, and closes the + connection. The class of the object passed is changed to + closed-database after the disconnection + succeeds, thereby preventing further use of the object as + an argument to &clsql; functions, + with the exception of database-name. + If the user does pass a closed database object to any other + &clsql; function, an error of type + clsql-closed-error is + signalled. + + + Examples + +(disconnect :database (find-database "dent/newesim/dent")) +=> T + + + + Side Effects + The database connection is closed, and the database + object is removed from the list of connected databases as + returned by connected-databases. + The class of the database object is changed to + closed-database. + If the database object passed is the same under + eq as the value of + *default-database*, then + *default-database* is set to the first + remaining database from + connected-databases or to nil if no + further active database exists. + + + Affected by + + + *default-database* + + + + + Exceptional Situations + If during the disconnection attempt an error is + detected (e.g. because of network trouble or any other + cause), an error of type clsql-error + might be signalled. + + + See Also + + + connect + closed-database + + + + + Notes + None. + + + + + + DISCONNECT-POOLED + closes all pooled database connections + Function + + + Syntax + disconnect-pool => t + + + Description + This function disconnects all database connections + that have been placed into the pool. Connections are placed + in the pool by calling disconnection. + + + + Examples + +(disconnect-pool) +=> T + + + + Side Effects + Database connections will be closed and entries in the pool are removed. + + + + Affected by + + + disconnect + + + + + Exceptional Situations + If during the disconnection attempt an error is + detected (e.g. because of network trouble or any other + cause), an error of type clsql-error + might be signalled. + + + See Also + + + connect + closed-database + + + + + Notes + None. + + + + + + CREATE-DATABASE + create a database + Function + + + Syntax + create-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, a new database was + successfully created. + + + + + + + Description + This function creates a database in the database system + specified by database-type. + + + + Examples + +(create-database '("localhost" "new" "dent" "dent") :database-type :mysql) +=> T + +(create-database '("localhost" "new" "dent" "badpasswd") :database-type :mysql) +=> +Error: While trying to access database localhost/new/dent + using database-type MYSQL: + Error database-create failed: mysqladmin: connect to server at 'localhost' failed +error: 'Access denied for user: 'root@localhost' (Using password: YES)' + has occurred. + [condition type: CLSQL-ACCESS-ERROR] + + + + Side Effects + A database will be created on the filesystem of the host. + + + Exceptional Situations + An exception will be thrown if the database system does + not allow new databases to be created or if database creation + fails. + + + Notes + This function may invoke the operating systems + functions. Thus, some database systems may require the + administration functions to be available in the current + PATH. At this time, the + :mysql backend requires + mysqladmin and the + :postgresql backend requires + createdb. + + + + + + DESTROY-DATABASE + destroys a database + Function + + + Syntax + destroy-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, the database was + successfully destroyed. + + + + + + + Description + This function destroy a database in the database system + specified by database-type. + + + + Examples + +(destroy-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> T + +(destroy-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> +Error: While trying to access database localhost/test2/root + using database-type POSTGRESQL: + Error database-destroy failed: dropdb: database removal failed: ERROR: database "test2" does not exist + has occurred. + [condition type: CLSQL-ACCESS-ERROR] + + + + Side Effects + A database will be removed from the filesystem of the host. + + + Exceptional Situations + An exception will be thrown if the database system does not + allow databases to be removed, the database does not exist, or + if database removal fails. + + + Notes + This function may invoke the operating systems + functions. Thus, some database systems may require the + administration functions to be available in the current + PATH. At this time, the + :mysql backend requires + mysqladmin and the + :postgresql backend requires + dropdb. + + + + + + PROBE-DATABASE + tests for existence of a database + Function + + + Syntax + probe-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, the database exists + in the database system. + + + + + + + Description + This function tests for the existence of a database in + the database system specified by + database-type. + + + + Examples + +(probe-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> T + + + + Side Effects + None + + + Exceptional Situations + An exception maybe thrown if the database system does + not receive administrator-level authentication since function + may need to read the administrative database of the database + system. + + + Notes + None. + + + + + + DATABASE-NAME-FROM-SPEC + Return the database name string corresponding to + the given connection specification. + Generic Function + + + Syntax + + database-name-from-spec connection-spec database-type => name + + + Arguments and Values + + + connection-spec + + A connection specification, whose structure and + interpretation are dependent on the + database-type. + + + + database-type + + A database type specifier, i.e. a keyword. + + + + name + + A string denoting a database name. + + + + + + Description + This generic function takes a connection specification + and a database type and returns the database name of the + database object that would be created had + connect been called with the given + connection specification and database types. + This function is useful in determining a database name + from the connection specification, since the way the + connection specification is converted into a database name + is dependent on the database type. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + +(find-database "dent/newesim/dent") +=> #<CLSQL-MYSQL:MYSQL-DATABASE {484E91C5}> +(find-database "/template1/dent") +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(find-database "www.pmsf.de/template1/dent" nil) +=> NIL +(find-database **) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> + + + + Side Effects + None. + + + Affected by + None. + + + Exceptional Situations + If the value of connection-spec + is not a valid connection specification for the given + database type, an error of type + clsql-invalid-spec-error might be + signalled. + + + See Also + + + connect + + + + + Notes + None. + + + + + + EXECUTE-COMMAND + Execute an SQL command which returns no + values. + Function + + + Syntax + execute-command sql-expression &key database => t + + + Arguments and Values + + + sql-expression + + An sql + expression that represents an SQL + statement which will return no values. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + This will execute the command given by + sql-expression in the + database specified. If the execution + succeeds it will return t, otherwise an + error of type sql-database-error will + be signalled. + + + Examples + +(execute-command "create table eventlog (time char(30),event char(70))") +=> T + +(execute-command "create table eventlog (time char(30),event char(70))") +>> +>> While accessing database #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {480B2B6D}> +>> with expression "create table eventlog (time char(30),event char(70))": +>> Error NIL: ERROR: amcreate: eventlog relation already exists +>> has occurred. +>> +>> Restarts: +>> 0: [ABORT] Return to Top-Level. +>> +>> Debug (type H for help) +>> +>> (CLSQL-POSTGRESQL::|(PCL::FAST-METHOD DATABASE-EXECUTE-COMMAND (T POSTGRESQL-DATABASE))| +>> #<unused-arg> +>> #<unused-arg> +>> #<unavailable-arg> +>> #<unavailable-arg>) +>> Source: (ERROR 'SQL-DATABASE-ERROR :DATABASE DATABASE :EXPRESSION ...) +>> 0] 0 + +(execute-command "drop table eventlog") +=> T + + + + Side Effects + Whatever effects the execution of the SQL statement has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL statement leads to any + errors, an error of type + sql-database-error is signalled. + + + See Also + + + query + + + + + Notes + None. + + + + + QUERY + Execute an SQL query and return the tuples as a + list + Function + + + Syntax + query query-expression &key database result-types field-names => result + + + Arguments and Values + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result-types + + A + field type + specifier. The default is &nil;. + + + The purpose of this argument is cause &clsql; to + import SQL numeric fields into numeric Lisp objects + rather than strings. This reduces the cost of + allocating a temporary string and the &clsql; users' + inconvenience of converting number strings into number + objects. + + + A value of :auto causes &clsql; + to automatically convert SQL fields into a + numeric format where applicable. The default value of + &nil; causes all fields to be returned as strings + regardless of the SQL type. Otherwise a list is expected + which has a element for each field that specifies the + conversion. If the list is shorter than the number + of fields, the a value of t is + assumed for the field. If the list is longer than + the number of fields, the extra elements are + ignored. + + :int Field is imported as a + signed integer, from 8-bits to 64-bits depending + upon the field type. + + :double Field is imported as a + double-float number. + + t Field is imported as a + string. + + + + + + + field-names + + + A boolean with a default value of &t;. When &t;, this + function results a second value of a list of field + names. When &nil;, this function only returns one value + - the list of rows. + + + + + result + + A list representing the result set obtained. For + each tuple in the result set, there is an element in + this list, which is itself a list of all the attribute + values in the tuple. + + + + + + Description + This will execute the query given by + query-expression in the + database specified. If the execution + succeeds it will return the result set returned by the + database, otherwise an error of type + sql-database-error will + be signalled. + + + Examples + +(execute-command "create table simple (name char(50), salary numeric(10,2))") +=> T +(execute-command "insert into simple values ('Mai, Pierre',10000)") +=> T +(execute-command "insert into simple values ('Hacker, Random J.',8000.50)") +=> T +(query "select * from simple") +=> (("Mai, Pierre" "10000.00") ("Hacker, Random J." "8000.50")) +(query "select salary from simple") +=> (("10000.00") ("8000.50")) +(query "select salary from simple where salary > 10000") +=> NIL +(query "select salary,name from simple where salary > 10000") +=> NIL +(query "select salary,name from simple where salary > 9000") +=> (("10000.00" "Mai, Pierre")) +(query "select salary,name from simple where salary > 8000") +=> (("10000.00" "Mai, Pierre") ("8000.50" "Hacker, Random J.")) + +;; MySQL-specific: +(query "show tables") +=> (("demo") ("log") ("newlog") ("simple") ("spacetrial")) + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + + + See Also + + + execute-command + + + + + Notes + None. + + + + + + MAP-QUERY + Map a function over all the tuples from a + query + Function + + + Syntax + map-query output-type-spec function query-expression &key database result-types => result + + + Arguments and Values + + + output-type-spec + + A sequence type specifier or nil. + + + + function + + A function designator. + function takes a single argument which + is the atom value for a query single with a single column + or is a list of values for a multi-column query. + + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result-types + + + A field type specifier. + The default is &nil;. See query + for the semantics of this argument. + + + + + result + + If output-type-spec is a + type specifier other than nil, then a + sequence of the type it denotes. Otherwise + nil is returned. + + + + + + Description + Applies function to the + successive tuples in the result set returned + by executing the SQL + query-expression. If the + output-type-spec is + nil, then the result of each application + of function is discarded, and + map-query returns + nil. Otherwise the result of each + successive application of function is + collected in a sequence of type + output-type-spec, where the jths + element is the result of applying + function to the attributes of the + jths tuple in the result set. The collected sequence is the + result of the call to map-query. + + If the output-type-spec is a + subtype of list, the result will be a + list. + If the result-type is a subtype + of vector, then if the implementation can + determine the element type specified for the + result-type, the element type of the + resulting array is the result of + upgrading that element type; or, if the + implementation can determine that the element type is + unspecified (or *), the element type of the + resulting array is t; otherwise, an error is + signaled. + + + Examples + +(map-query 'list #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (declare (ignorable name)) + (read-from-string salary))) + "select salary,name from simple where salary > 8000") +=> (10000.0 8000.5) + +(map-query '(vector double-float) + #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (declare (ignorable name)) + (let ((*read-default-float-format* 'double-float)) + (coerce (read-from-string salary) 'double-float)) + "select salary,name from simple where salary > 8000"))) +=> #(10000.0d0 8000.5d0) +(type-of *) +=> (SIMPLE-ARRAY DOUBLE-FLOAT (2)) + +(let (list) + (values (map-query nil #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (push (cons name (read-from-string salary)) list)) + "select salary,name from simple where salary > 8000") + list)) +=> NIL +=> (("Hacker, Random J." . 8000.5) ("Mai, Pierre" . 10000.0)) + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + An error of type type-error must + be signaled if the output-type-spec is + not a recognizable subtype of list, not a + recognizable subtype of vector, and not + nil. + An error of type type-error + should be signaled if + output-type-spec specifies the number + of elements and the size of the result set is different from + that number. + + + See Also + + + query + do-query + + + + + Notes + None. + + + + + DO-QUERY + Iterate over all the tuples of a + query + Macro + + + Syntax + do-query ((&rest args) query-expression &key database result-types) &body body => nil + + + Arguments and Values + + + args + + A list of variable names. + + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set, where each tuple has as many attributes as + function takes arguments. + + + + database + + A + database + object. This will default to + *default-database*. + + + + result-types + + + A field type specifier. + The default is &nil;. See query + for the semantics of this argument. + + + + + body + + A body of Lisp code, like in a + destructuring-bind form. + + + + + + Description + Executes the body of code + repeatedly with the variable names in + args bound to the attributes of each + tuple in the result set returned by executing the SQL + query-expression on the + database specified. + The body of code is executed in a block named + nil which may be returned from prematurely + via return or + return-from. In this case the result + of evaluating the do-query form will be + the one supplied to return or + return-from. Otherwise the result will + be nil. + The body of code appears also is if wrapped in a + destructuring-bind form, thus allowing + declarations at the start of the body, especially those + pertaining to the bindings of the variables named in + args. + + + Examples + +(do-query ((salary name) "select salary,name from simple") + (format t "~30A gets $~2,5$~%" name (read-from-string salary))) +>> Mai, Pierre gets $10000.00 +>> Hacker, Random J. gets $08000.50 +=> NIL + +(do-query ((salary name) "select salary,name from simple") + (return (cons salary name))) +=> ("10000.00" . "Mai, Pierre") + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + If the number of variable names in + args and the number of attributes in + the tuples in the result set don't match up, an error is + signalled. + + + See Also + + + query + map-query + + + + + Notes + None. + + + + + LOOP-FOR-AS-TUPLES + Iterate over all the tuples of a + query via a loop clause + Loop Clause + + + Compatibility + loop-for-as-tuples only works with &cmucl;. + + + Syntax + var [type-spec] being {each | the} {record | records | tuple | tuples} {in | of} query [from database] + + + Arguments and Values + + + var + + A d-var-spec, as defined in the + grammar for loop-clauses in the + ANSI Standard for Common Lisp. This allows for the + usual loop-style destructuring. + + + + type-spec + + An optional type-spec either + simple or destructured, as defined in the grammar for + loop-clauses in the ANSI Standard + for Common Lisp. + + + + query + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set, where each tuple has as many attributes as + function takes arguments. + + + + database + + An optional + database + object. This will default to the value + of *default-database*. + + + + + + Description + This clause is an iteration driver for + loop, that binds the given variable + (possibly destructured) to the consecutive tuples (which are + represented as lists of attribute values) in the result set + returned by executing the SQL query + expression on the database + specified. + + + Examples + +(defvar *my-db* (connect '("dent" "newesim" "dent" "dent")) + "My database" +=> *MY-DB* +(loop with time-graph = (make-hash-table :test #'equal) + with event-graph = (make-hash-table :test #'equal) + for (time event) being the tuples of "select time,event from log" + from *my-db* + do + (incf (gethash time time-graph 0)) + (incf (gethash event event-graph 0)) + finally + (flet ((show-graph (k v) (format t "~40A => ~5D~%" k v))) + (format t "~&Time-Graph:~%===========~%") + (maphash #'show-graph time-graph) + (format t "~&~%Event-Graph:~%============~%") + (maphash #'show-graph event-graph)) + (return (values time-graph event-graph))) +>> Time-Graph: +>> =========== +>> D => 53000 +>> X => 3 +>> test-me => 3000 +>> +>> Event-Graph: +>> ============ +>> CLOS Benchmark entry. => 9000 +>> Demo Text... => 3 +>> doit-text => 3000 +>> C Benchmark entry. => 12000 +>> CLOS Benchmark entry => 32000 +=> #<EQUAL hash table, 3 entries {48350A1D}> +=> #<EQUAL hash table, 5 entries {48350FCD}> + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + Otherwise, any of the exceptional situations of + loop applies. + + + See Also + + + query + map-query + do-query + + + + + Notes + None. + + + diff --git a/doc/ref-conditions.xml b/doc/ref-conditions.xml new file mode 100644 index 0000000..992fa9a --- /dev/null +++ b/doc/ref-conditions.xml @@ -0,0 +1,813 @@ + + +%myents; +]> + + + + + CLSQL Condition System + + + &clsql; provides and uses a condition system in which all errors + and warnings are of type sql-condition. This + section describes the various subclasses of sql-condition + defined by &clsql;. Details are also provided for how they are + used in &clsql; and intended to be signalled in user + code. Finally, slot accessors for some of the condition types + are described. + + + + + + *BACKEND-WARNING-BEHAVIOR* + + + *BACKEND-WARNING-BEHAVIOR* + Controls behaviour on warnings from underlying RDBMS. + Variable + + + Value Type + + Meaningful values are :warn, + :error, :ignore and &nil;. + + + + Initial Value + :warn + + + Description + + Action to perform on warning messages + from backend. Default is to :warn. May also be + set to :error to signal an error or + :ignore or &nil; to silently ignore the + warning. + + + + Examples + + + + + + Affected By + None. + + + See Also + None. + + + Notes + *backend-warning-behaviour* is a &clsql; + extension. + + + + + + SQL-CONDITION + + + SQL-CONDITION + the super-type of all + &clsql;-specific + conditions + Condition Type + + + Class Precedence List + + + sql-condition + condition + t + + + + + Description + + This is the super-type of all &clsql;-specific conditions + defined by &clsql;, or any of it's database-specific + interfaces. There are no defined initialization arguments nor + any accessors. + + + + Notes + sql-condition is a &clsql; + extension. + + + + + + SQL-ERROR + + + SQL-ERROR + the super-type of all + &clsql;-specific + errors + Condition Type + + + Class Precedence List + + + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + + This is the super-type of all &clsql;-specific conditions that + represent errors, as defined by &clsql;, or any of it's + database-specific interfaces. There are no defined + initialization arguments nor any accessors. + + + + Notes + sql-error is a &clsql; extension. + + + + + + SQL-WARNING + + + SQL-WARNING + the super-type of all + &clsql;-specific + warnings + Condition Type + + + Class Precedence List + + + sql-warning + warning + sql-condition + condition + t + + + + + Description + + This is the super-type of all &clsql;-specific conditions that + represent warnings, as defined by &clsql;, or any of it's + database-specific interfaces. There are no defined + initialization arguments nor any accessors. + + + + Notes + sql-warning is a &clsql; extension. + + + + + + SQL-DATABASE-WARNING + + + SQL-DATABASE-WARNING + Used to warn while accessing a + &clsql; database. + Condition Type + + + Class Precedence List + + + sql-database-warning + sql-warning + warning + sql-condition + condition + t + + + + + Description + + This condition represents warnings signalled while accessing + a database. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :database + sql-warning-database + The database object that was involved in the + incident. + + + + + Notes + sql-database-warning is a &clsql; + extension. + + + + + + + + SQL-USER-ERROR + + + SQL-USER-ERROR + condition representing errors because of invalid + parameters from the library user. + Condition Type + + + Class Precedence List + + + sql-user-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + + This condition represents errors that occur because the user + supplies invalid data to &clsql;. This includes errors such + as an invalid format connection specification or an error in + the syntax for the LOOP macro extensions. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :message + sql-user-error-message + The error message. + + + + + Notes + The slot accessor + sql-user-error-message is a &clsql; + extension. + + + + + + SQL-DATABASE-ERROR + + + SQL-DATABASE-ERROR + condition representing errors during query or + command execution + Condition Type + + + Class Precedence List + + + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + + This condition represents errors that occur while executing + SQL statements, either as part of query operations or command + execution, either explicitly or implicitly, as caused e.g. by + with-transaction. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-error-database-message + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessor + sql-error-database is a &clsql; + extension. + + + + + + + SQL-CONNECTION-ERROR + + + SQL-CONNECTION-ERROR + condition representing errors during + connection + Condition Type + + + Class Precedence List + + + sql-connection-error + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + + This condition represents errors that occur while trying to + connect to a database. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :database-type + sql-error-database-type + Database type for the connection attempt + + + :connection-spec + sql-error-connection-spec + The connection specification used in the + connection attempt. + + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-database-error-error + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessors + sql-error-database, + sql-error-database-type and + sql-error-connection-spec are + &clsql; extensions. + + + + + + + SQL-DATABASE-DATA-ERROR + + + SQL-DATABASE-DATA-ERROR + Used to signal an error with the SQL data passed + to a database. + Condition Type + + + Class Precedence List + + + sql-database-data-error + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + This condition represents errors that occur while + executing SQL statements, specifically as a result of + malformed SQL expressions. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :expression + sql-error-expression + The SQL expression whose execution caused the error. + + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-error-database-message + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessors + sql-error-database and + sql-error-expression are + &clsql; extensions. + + + + + + + SQL-TEMPORARY-ERROR + + + SQL-TEMPORARY-ERROR + Used to signal a temporary error in the database + backend. + Condition Type + + + Class Precedence List + + + sql-temporary-error + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + + This condition represents errors occurring when the database + cannot currently process a valid interaction because, for + example, it is still executing another command possibly issued + by another user. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-error-database-message + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessor + sql-error-database is a &clsql; + extension. + + + + + + + SQL-TIMEOUT-ERROR + + + SQL-TIMEOUT-ERROR + condition representing errors when a connection + times out. + Condition Type + + + Class Precedence List + + + sql-connection-error + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + This condition represents errors that occur when the + database times out while processing some operation. The + following initialization arguments and accessors exist: + + Initarg + Accessor + Description + + :database-type + sql-error-database-type + Database type for the connection attempt + + + :connection-spec + sql-error-connection-spec + The connection specification used in the + connection attempt. + + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-error-database-message + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessors + sql-error-database, + sql-error-database-type and + sql-error-connection-spec are + &clsql; extensions. + + + + + + + SQL-FATAL-ERROR + + + SQL-FATAL-ERROR + condition representing a fatal error in a database + connection + Condition Type + + + Class Precedence List + + + sql-connection-error + sql-database-error + sql-error + simple-error + simple-condition + error + serious-condition + sql-condition + condition + t + + + + + Description + This condition represents errors occurring when the + database connection is no longer usable. + + + The following initialization arguments and accessors exist: + + + Initarg + Accessor + Description + + :database-type + sql-error-database-type + Database type for the connection attempt + + + :connection-spec + sql-error-connection-spec + The connection specification used in the + connection attempt. + + + :database + sql-error-database + The database object that was involved in the + incident. + + + :error-id + sql-error-error-id + The numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :secondary-error-id + sql-error-secondary-error-id + The secondary numeric or symbolic error specification + returned by the database back-end. The values and + semantics of this are interface specific. + + + :message + sql-error-database-message + A string describing the problem that occurred, + possibly one returned by the database back-end. + + + + + Notes + + The slot accessors + sql-error-database, + sql-error-database-type and + sql-error-connection-spec are + &clsql; extensions. + + + + + diff --git a/doc/ref-connect.xml b/doc/ref-connect.xml new file mode 100644 index 0000000..1e299b9 --- /dev/null +++ b/doc/ref-connect.xml @@ -0,0 +1,2364 @@ + + +%myents; +]> + + + Connection and Initialisation + + + This section describes the &clsql; interface for initialising + database interfaces of different types, creating and destroying + databases and connecting and disconnecting from databases. + + + + + + + + DATABASE + + + DATABASE + The super-type of all &clsql; databases + Class + + + Class Precedence List + + + database + standard-object + t + + + + + Description This class is the superclass of + all &clsql; databases. The different database back-ends derive + subclasses of this class to implement their databases. No + instances of this class are ever created by &clsql;. + + + + + + + *CONNECT-IF-EXISTS* + + + *CONNECT-IF-EXISTS* + Default value for the + if-exists parameter of connect. + Variable + + + Value Type + A valid argument to the if-exists + parameter of connect, that is, one of + + :new + :warn-new + :error + :warn-old + :old + . + + + + Initial Value + :error + + + Description + The value of this variable is used in calls to + connect as the default + value of the if-exists + parameter. See connect for + the semantics of the valid values for this variable. + + + Examples + None. + + + Affected By + None. + + + See Also + + + connect + + + + + Notes + None. + + + + + *DB-POOL-MAX-FREE-CONNECTIONS* + + + *DB-POOL-MAX-FREE-CONNECTIONS* + How many free connections should the connection pool try to keep. + Parameter + + + Value Type + Integer + + + Initial Value + 4 + + + Description + Threshold of free-connections in the pool before we + disconnect a database rather than returning it to the pool. NIL for + no limit. This is really a heuristic that should, on avg keep the + free connections about this size. + + This is not a hard limit, the number of connections in + the pool may exceed this value. + + + + Examples + + (setf clsql-sys:*db-pool-max-free-connections* 2) + + + + Affected By + None + + + See Also + + + connect + disconnect + + + + + Notes + + + + + + + + + + *DEFAULT-DATABASE* + + + *DEFAULT-DATABASE* + The default database object to use. + Variable + + + Value Type + Any object of type database, or &nil; to + indicate no default database. + + + Initial Value + &nil; + + + Description + Any function or macro in &clsql; that operates on a + database uses the value of this variable as the default value + for it's database parameter. + The value of this parameter is changed by calls to + connect, which sets + *default-database* to the database object + it returns. It is also changed by calls to + disconnect, when the database object + being disconnected is the same as the value of + *default-database*. In this case + disconnect sets + *default-database* to the first database + that remains in the list of active databases as returned by + connected-databases, or + &nil; if no further active databases + exist. + The user may change *default-database* + at any time to a valid value of his choice. + + If the value of *default-database* is + &nil;, then all calls to &clsql; functions on + databases must provide a suitable + database parameter, or an error will be + signalled. + + + + Examples + + (connected-databases) + => NIL + (connect '("dent" "newesim" "dent" "dent") :database-type :mysql) + => #<CLSQL-MYSQL:MYSQL-DATABASE {48385F55}> + (connect '(nil "template1" "dent" nil) :database-type :postgresql) + => #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {483868FD}> + (connect '("dent" "newesim" "dent" "dent") :database-type :mysql :if-exists :new) + => #<CLSQL-MYSQL:MYSQL-DATABASE {48387265}> + *default-database* + => #<CLSQL-MYSQL:MYSQL-DATABASE {48387265}> + (disconnect) + => T + *default-database* + => #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {483868FD}> + (disconnect) + => T + *default-database* + => #<CLSQL-MYSQL:MYSQL-DATABASE {48385F55}> + (disconnect) + => T + *default-database* + => NIL + (connected-databases) + => NIL + + + + Affected By + + connect + disconnect + + + + See Also + + + connected-databases + + + + + Notes + + This variable is intended to facilitate working with + &clsql; in an interactive + fashion at the top-level loop, and because of this, + connect and + disconnect provide some fairly + complex behaviour to keep + *default-database* set to useful values. + Programmatic use of &clsql; + should never depend on the value of + *default-database* and should provide + correct database objects via the + database parameter to functions + called. + + + + + + + *DEFAULT-DATABASE-TYPE* + + + *DEFAULT-DATABASE-TYPE* + The default database type to use + Variable + + + Value Type + Any keyword representing a valid database back-end of + &clsql;, or &nil;. + + + Initial Value + &nil; + + + Description + The value of this variable is used in calls to + initialize-database-type and + connect as the default value of the + database-type parameter. + + If the value of this variable is &nil;, + then all calls to + initialize-database-type or + connect will have to specify the + database-type to use, or a + general-purpose error will be signalled. + + + + Examples + + (setf *default-database-type* :mysql) + => :mysql + (initialize-database-type) + => t + + + + Affected By + None. + + + See Also + + intitialize-database-type + + + + Notes + None. + + + + + + *INITIALIZED-DATABASE-TYPES* + + + *INITIALIZED-DATABASE-TYPES* + List of all initialized database types + Variable + + + Value Type + A list of all initialized database types, each of which + represented by it's corresponding keyword. + + + Initial Value + &nil; + + + Description + This variable is updated whenever + initialize-database-type is called for a + database type which hasn't already been initialized before, as + determined by this variable. In that case the keyword + representing the database type is pushed onto the list stored in + *INITIALIZED-DATABASE-TYPES*. + + Attempts to modify the value of this variable will + result in undefined behaviour. + + + + Examples + + (setf *default-database-type* :mysql) + => :mysql + (initialize-database-type) + => t + *initialized-database-types* + => (:MYSQL) + + + + Affected By + + + initialize-database-type + + + + + See Also + + intitialize-database-type + + + + Notes + Direct access to this variable is primarily provided + because of compatibility with Harlequin's Common + SQL. + + + + + + CONNECT + + + CONNECT + create a connection to a database. + Function + + + Syntax + connect connection-spec &key if-exists database-type pool make-default => database + + + Arguments and Values + + + connection-spec + + A SQL backend specific connection specification + supplied as a list or as a string. + For the MySQL backend, this list includes an + optional associative list of connection options. The + options list is parsed and supplied to the MySQL API + using mysql_options in between the + calls to mysql_init + and mysql_real_connect. + + + + if-exists + + This indicates the action to take if a connection + to the same database exists already. See below for the + legal values and actions. It defaults to the value of + *connect-if-exists*. + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + pool + + A boolean flag. If &t;, acquire connection from a + pool of open connections. If the pool is empty, a new + connection is created. The default is &nil;. + + + + + make-default + + A boolean flag. If &t;, + *default-database* is set to the new + connection, otherwise *default-database* + is not changed. The default is &t;. + + + + + database + + The database object representing the connection. + + + + + + Description + This function takes a connection specification and + a database type and creates a connection to the database + specified by those. The type and structure of the + connection specification depend on the database type. + The parameter if-exists specifies + what to do if a connection to the database specified exists + already, which is checked by calling + find-database on the database name + returned by database-name-from-spec + when called with the connection-spec + and database-type parameters. The + possible values of if-exists are: + + + :new + + Go ahead and create a new connection. + + + + :warn-new + + This is just like :new, but + also signals a warning of type + clsql-exists-warning, + indicating the old and newly created + databases. + + + + :error + + This will cause connect to + signal a correctable error of type + clsql-exists-error. The + user may choose to proceed, either by indicating + that a new connection shall be created, via the + restart create-new, or by + indicating that the existing connection shall be + used, via the restart + use-old. + + + + :old + + This will cause connect to + use an old connection if one exists. + + + + :warn-old + + This is just like :old, but + also signals a warning of type + clsql-exists-warning, + indicating the old database used, via the slots + old-db and + new-db + + + + + The database name of the returned database object will + be the same under string= as that which + would be returned by a call to + database-name-from-spec with the given + connection-spec and + database-type parameters. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48036F6D}> +(database-name *) +=> "dent/newesim/dent" + +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +>> In call to CONNECT: +>> There is an existing connection #<CLSQL-MYSQL:MYSQL-DATABASE {48036F6D}> to database dent/newesim/dent. +>> +>> Restarts: +>> 0: [CREATE-NEW] Create a new connection. +>> 1: [USE-OLD ] Use the existing connection. +>> 2: [ABORT ] Return to Top-Level. +>> +>> Debug (type H for help) +>> +>> (CONNECT ("dent" "newesim" "dent" "dent") :IF-EXISTS NIL :DATABASE-TYPE ...) +>> Source: +>> ; File: /prj/CLSQL/sql/sql.cl +>> (RESTART-CASE (ERROR 'CLSQL-EXISTS-ERROR :OLD-DB OLD-DB) +>> (CREATE-NEW NIL :REPORT "Create a new connection." +>> (SETQ RESULT #)) +>> (USE-OLD NIL :REPORT "Use the existing connection." +>> (SETQ RESULT OLD-DB))) +>> 0] 0 +=> #<CLSQL-MYSQL:MYSQL-DATABASE {480451F5}> + + + + Side Effects + A database connection is established, and the resultant + database object is registered, so as to appear in the list + returned by connected-databases. + *default-database* may be rebound to the + created object. + + + Affected by + + + + + *default-database-type* + + + + + *connect-if-exists* + + + + + + + Exceptional Situations + If the connection specification is not syntactically or + semantically correct for the given database type, an error of + type sql-user-error is + signalled. If during the connection attempt an error is + detected (e.g. because of permission problems, network trouble + or any other cause), an error of type + sql-database-error is signalled. + If a connection to the database specified by + connection-spec exists already, + conditions are signalled according to the + if-exists parameter, as described + above. + + + See Also + + connected-databases + disconnect + reconnect + *connect-if-exists* + find-database + status + + + + Notes + The pool and + make-default keyword arguments to + connect are &clsql; extensions. + + + + + + CONNECTED-DATABASES + + + CONNECTED-DATABASES + Return the list of active database objects. + Function + + + Syntax + + connected-databases => databases + + + Arguments and Values + + + databases + + The list of active database objects. + + + + + + Description + This function returns the list of active database + objects, i.e. all those database objects created by calls to + connect, which have not been closed by + calling disconnect on them. + + The consequences of modifying the list returned by + connected-databases are + undefined. + + + + Examples + +(connected-databases) +=> NIL +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}> +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {4830C5AD}> +(connected-databases) +=> (#<CLSQL-MYSQL:MYSQL-DATABASE {4830C5AD}> + #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}>) +(disconnect) +=> T +(connected-databases) +=> (#<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {4830BC65}>) +(disconnect) +=> T +(connected-databases) +=> NIL + + + + Side Effects + None. + + + Affected By + + + connect + disconnect + + + + + Exceptional Situations + None. + + + See Also + + disconnect + connect + status + find-database + + + + Notes + None. + + + + + + + DATABASE-NAME + + + DATABASE-NAME + Get the name of a database object + Generic Function + + + Syntax + database-name database => name + + + Arguments and Values + + + database + + A database object, either of type + database or of type + closed-database. + + + + name + + A string describing the identity of the database + to which this database object is connected to. + + + + + + Description + This function returns the database name of the given + database. The database name is a string which somehow + describes the identity of the database to which this + database object is or has been connected. The database name + of a database object is determined at + connect time, when a call to + database-name-from-spec derives the + database name from the connection specification passed to + connect in the + connection-spec parameter. + The database name is used via + find-database in + connect to determine whether database + connections to the specified database exist already. + Usually the database name string will include + indications of the host, database name, user, or port that + where used during the connection attempt. The only + important thing is that this string shall try to identify + the database at the other end of the connection. Connection + specifications parts like passwords and credentials shall + not be used as part of the database name. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + + + + Side Effects + None. + + + Affected By + + + database-name-from-spec + + + + + Exceptional Situations + Will signal an error if the object passed as the + database parameter is neither of type + database nor of type + closed-database. + + + See Also + + + connect + find-database + connected-databases + disconnect + status + + + + + Notes + None. + + + + + + DATABASE-NAME-FROM-SPEC + + + DATABASE-NAME-FROM-SPEC + Return the database name string corresponding to + the given connection specification. + Generic Function + + + Syntax + + database-name-from-spec connection-spec database-type => name + + + Arguments and Values + + + connection-spec + + A connection specification, whose structure and + interpretation are dependent on the + database-type. + + + + database-type + + A database type specifier, i.e. a keyword. + + + + name + + A string denoting a database name. + + + + + + Description + This generic function takes a connection specification + and a database type and returns the database name of the + database object that would be created had + connect been called with the given + connection specification and database types. + This function is useful in determining a database name + from the connection specification, since the way the + connection specification is converted into a database name + is dependent on the database type. + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + +(find-database "dent/newesim/dent") +=> #<CLSQL-MYSQL:MYSQL-DATABASE {484E91C5}> +(find-database "/template1/dent") +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(find-database "www.pmsf.de/template1/dent" nil) +=> NIL +(find-database **) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> + + + + Side Effects + None. + + + Affected by + None. + + + Exceptional Situations + If the value of connection-spec + is not a valid connection specification for the given + database type, an error of type + clsql-invalid-spec-error might be + signalled. + + + See Also + + + connect + + + + + Notes + database-name-from-spec is a + &clsql; extension. + + + + + + DATABASE-TYPE + + + DATABASE-TYPE + Get the type of a database object. + Generic Function + + + Syntax + + database-type DATABASE => type + + + Arguments and Values + + + database + + A database object, either of type + database or of type + closed-database. + + + + type + + A keyword symbol denoting a known database back-end. + + + + + + Description + + Returns the type of database. + + + + Examples + +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-type *default-database*) +=> :postgresql + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + Will signal an error if the object passed as the + database parameter is neither of type + database nor of type + closed-database. + + + See Also + + + connect + find-database + connected-databases + disconnect + status + + + + + Notes + + database-type is a &clsql; extension. + + + + + + + DISCONNECT + + + DISCONNECT + close a database connection + Function + + + Syntax + disconnect &key database error => result + + + Arguments and Values + + + error + + A boolean flag indicating whether to signal an error + if database is non-&nil; but cannot + be found. + + + + + database + + The database to disconnect, which defaults to the + database indicated by + *default-database*. + + + + result + + A Boolean indicating whether a connection was + successfully disconnected. + + + + + + + Description + This function takes a database object as + returned by connect, and closes the + connection. If no matching database is found and + error and + database are both non-&nil; an error is + signaled, otherwise &nil; is returned. If the database is from a + pool it will be released to this pool. + + + The status of the object passed is changed to closed + after the disconnection succeeds, thereby preventing further + use of the object as an argument to &clsql; functions, with + the exception of database-name and + database-type. If the user does pass a + closed database to any other &clsql; function, an error of + type sql-fatal-error is + signalled. + + + Examples + +(disconnect :database (find-database "dent/newesim/dent")) +=> T + + + + Side Effects + The database object is removed from the list of connected databases as + returned by connected-databases. + If the database object passed is the same under + eq as the value of + *default-database*, then + *default-database* is set to the first + remaining database from + connected-databases or to &nil; if no + further active database exists. + + Non-pooled + The database connection is closed and the state of the + database object is changed to closed. + + + Pooled + Unless there are already + *db-pool-max-free-connections* + free connections in the pool it is returned to the + pool, with the backend having an opportunity to run + generic cleanup on the connection first. If the max free + connections has already been reached then it is + disconnected as if it were not in the pool. + + + + + Affected by + + + + + *default-database* + + + + + *db-pool-max-free-connections* + + + + + + + Exceptional Situations + If during the disconnection attempt an error is detected + (e.g. because of network trouble or any other cause), an error + of type sql-error might be + signalled. + + + See Also + + + connect + disconnect-pooled + + + + + Notes + None. + + + + + + DISCONNECT-POOLED + + + DISCONNECT-POOLED + closes all pooled database connections + Function + + + Syntax + disconnect-pooled &optional clear => t + + + Description + This function disconnects all database connections + that have been placed into the pool by calling connect with + :pool &t;. + + If optional argument clear is non-&nil; + then the connection-pool objects are also removed. + + + Examples + +(disconnect-pool) +=> T + + + + Side Effects + Database connections will be closed and *all* entries in + the pool are removed. This is done with great prejudice and no + thought to thread safety or whether that connection is + currently in use. + + + Affected by + + + disconnect + + + + + Exceptional Situations + If during the disconnection attempt an error is + detected (e.g. because of network trouble or any other + cause), an error of type clsql-error + might be signalled. + + + See Also + + + connect + disconnect + + + + + Notes + disconnect-pooled is a &clsql; + extension. + + + + + + FIND-DATABASE + + + FIND-DATABASE + >Locate a database object through it's + name. + Function + + + Syntax + find-database database &optional errorp => result + + + Arguments and Values + + + database + + A database object or a string, denoting a database + name. + + + + errorp + + A generalized boolean. Defaults to + t. + + + + db-type + + + A keyword symbol denoting a known database back-end. + + + + + result + + Either a database object, or, if + errorp is &nil;, + possibly &nil;. + + + + + + Description + find-database locates an active + database object given the specification in + database. If + database is an object of type + database, find-database + returns this. Otherwise it will search the active databases + as indicated by the list returned by + connected-databases for a database of + type db-type whose name (as returned by + database-name is equal as per + string= to the string passed as + database. If it succeeds, it returns + the first database found. + + If db-type is &nil; all databases + matching the string database are + considered. If no matching databases are found and + errorp is &nil; then &nil; is + returned. If errorp is &nil; and one or + more matching databases are found, then the most recently + connected database is returned as a first value and the + number of matching databases is returned as a second + value. If no, or more than one, matching databases are found + and errorp is true, an error is + signalled. + + + + Examples + +(database-name-from-spec '("dent" "newesim" "dent" "dent") :mysql) +=> "dent/newesim/dent" +(connect '("dent" "newesim" "dent" "dent") :database-type :mysql) +=> #<CLSQL-MYSQL:MYSQL-DATABASE {48391DCD}> +(database-name *default-database*) +=> "dent/newesim/dent" + +(database-name-from-spec '(nil "template1" "dent" nil) :postgresql) +=> "/template1/dent" +(connect '(nil "template1" "dent" nil) :database-type :postgresql) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(database-name *default-database*) +=> "/template1/dent" + +(database-name-from-spec '("www.pmsf.de" "template1" "dent" nil) :postgresql) +=> "www.pmsf.de/template1/dent" + +(find-database "dent/newesim/dent") +=> #<CLSQL-MYSQL:MYSQL-DATABASE {484E91C5}> +(find-database "/template1/dent") +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> +(find-database "www.pmsf.de/template1/dent" nil) +=> NIL +(find-database **) +=> #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {48392D2D}> + + + + Side Effects + None. + + + Affected By + + + connected-databases + + + + + Exceptional Situations + Will signal an error of type + clsql-error if no matching database + can be found, and errorp is true. + Will signal an error if the value of + database is neither an object of type + database nor a string. + + + See Also + + + database-name + database-name-from-spec + disconnect + connect + status + connected-databases + + + + + Notes + The db-type keyword argument to + find-database is a &clsql; + extension. + + + + + + INITIALIZE-DATABASE-TYPE + + + INITIALIZE-DATABASE-TYPE + Initializes a database type + Function + + + Syntax + + initialize-database-type &key database-type => result + + + Arguments and Values + + + database-type + + The database type to initialize, i.e. a keyword + symbol denoting a known database back-end. Defaults to + the value of + *default-database-type*. + + + + result + + Either &nil; if the initialization + attempt fails, or t otherwise. + + + + + + Description + If the back-end specified by + database-type has not already been + initialized, as seen from + *initialized-database-types*, an attempt is + made to initialize the database. If this attempt succeeds, + or the back-end has already been initialized, the function + returns t, and places the keyword denoting the database type + onto the list stored in + *initialized-database-types*, if not + already present. + If initialization fails, the function returns + &nil;, and/or signals an error of type + clsql-error. The kind of action + taken depends on the back-end and the cause of the + problem. + + + Examples + +*initialized-database-types* +=> NIL +(setf *default-database-type* :mysql) +=> :MYSQL +(initialize-database-type) +>> Compiling LAMBDA (#:G897 #:G898 #:G901 #:G902): +>> Compiling Top-Level Form: +>> +=> T +*initialized-database-types* +=> (:MYSQL) +(initialize-database-type) +=> T +*initialized-database-types* +=> (:MYSQL) + + + + Side Effects + The database back-end corresponding to the database type + specified is initialized, unless it has already been + initialized. This can involve any number of other side + effects, as determined by the back-end implementation (like + e.g. loading of foreign code, calling of foreign code, + networking operations, etc.). If initialization is + attempted and succeeds, the + database-type is pushed onto the list + stored in + *initialized-database-types*. + + + Affected by + + + *default-database-type* + *initialized-database-types* + + + + + Exceptional Situations + If an error is encountered during the initialization + attempt, the back-end may signal errors of kind + clsql-error. + + + See Also + + *initialized-database-types* + *default-database-type* + + + + Notes + None. + + + + + + RECONNECT + + + RECONNECT + Re-establishes the connection between a database object and its RDBMS. + Function + + + Syntax + + reconnect &key database error force => result + + + Arguments and Values + + + database + + The database to reconnect, which defaults to the + database indicated by + *default-database*. + + + + error + + A boolean flag indicating whether to signal an error + if database is non-nil but cannot + be found. The default value is &nil;. + + + + + force + + A Boolean indicating whether to signal an error if the + database connection has been lost. The default value is &t;. + + + + + result + + A Boolean indicating whether the database was + successfully reconnected. + + + + + + + Description + Reconnects database which defaults + to *default-database* to the underlying + database management system. On success, &t; is returned and the + variable *default-database* is set to the newly + reconnected database. If database is a + database instance, this object is closed. If + database is a string, then a connected + database whose name matches database is + sought in the list of connected databases. If no matching + database is found and error and + database are both non-&nil; an error is + signaled, otherwise &nil; is returned. + + When the current database connection has been lost, if + force is non-&nil; as it is by default, the + connection is closed and errors are suppressed. If + force is &nil; and the database connection + cannot be closed, an error is signalled. + + + + Examples + +*default-database* +=> #<CLSQL-SQLITE:SQLITE-DATABASE :memory: OPEN {48CFBEA5}> +(reconnect) +=> #<CLSQL-SQLITE:SQLITE-DATABASE :memory: OPEN {48D64105}> + + + + Side Effects + A database connection is re-established and + *default-database* may be rebound to the supplied + database object. + + + Affected by + + + *default-database* + + + + + Exceptional Situations + + An error may be signalled if the specified database cannot be + located or if the database cannot be closed. + + + + See Also + + + connect + disconnect + disconnect-pooled + + + + + Notes + + None. + + + + + + + STATUS + + + STATUS + Print information about connected databases. + Function + + + Syntax + + status &optional full => + + + Arguments and Values + + + full + + A boolean indicating whether to print additional + table information. The default value is &nil;. + + + + + + + Description + Prints information about the currently connected databases + to *STANDARD-OUTPUT*. The argument + full is &nil; by default and a value of t + means that more detailed information about each database is + printed. + + + + Examples + +(status) + +CLSQL STATUS: 2004-06-13 15:07:39 +-------------------------------------------------------- + DATABASE TYPE RECORDING +-------------------------------------------------------- + localhost/test/petrov mysql nil + localhost/test/petrov postgresql nil + localhost/test/petrov postgresql-socket nil + test/petrov odbc nil +* :memory: sqlite nil +-------------------------------------------------------- + +(status t) + +CLSQL STATUS: 2004-06-13 15:08:08 +------------------------------------------------------------------------------- + DATABASE TYPE RECORDING POOLED TABLES VIEWS +------------------------------------------------------------------------------- + localhost/test/petrov mysql nil nil 7 0 + localhost/test/petrov postgresql nil nil 7 0 + localhost/test/petrov postgresql-socket nil nil 7 0 + test/petrov odbc nil nil 7 0 +* :memory: sqlite nil nil 0 0 +------------------------------------------------------------------------------- + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + + connected-databases + connect + disconnect + *connect-if-exists* + find-database + + + + + Notes + + None. + + + + + + + + + + CREATE-DATABASE + + + CREATE-DATABASE + create a database + Function + + + Syntax + create-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, a new database was + successfully created. + + + + + + + Description + This function creates a database in the database system + specified by database-type. + + + + Examples + +(create-database '("localhost" "new" "dent" "dent") :database-type :mysql) +=> T + +(create-database '("localhost" "new" "dent" "badpasswd") :database-type :mysql) +=> +Error: While trying to access database localhost/new/dent + using database-type MYSQL: + Error database-create failed: mysqladmin: connect to server at 'localhost' failed +error: 'Access denied for user: 'root@localhost' (Using password: YES)' + has occurred. + [condition type: CLSQL-ACCESS-ERROR] + + + + Side Effects + A database will be created on the filesystem of the host. + + + Exceptional Situations + An exception will be thrown if the database system does + not allow new databases to be created or if database creation + fails. + + + See Also + + + destroy-database + probe-database + list-databases + + + + + Notes + This function may invoke the operating systems + functions. Thus, some database systems may require the + administration functions to be available in the current + PATH. At this time, the + :mysql backend requires + mysqladmin and the + :postgresql backend requires + createdb. + + create-database is a &clsql; extension. + + + + + + + DESTROY-DATABASE + + + DESTROY-DATABASE + destroys a database + Function + + + Syntax + destroy-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, the database was + successfully destroyed. + + + + + + + Description + This function destroys a database in the database system + specified by database-type. + + + + Examples + +(destroy-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> T + +(destroy-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> +Error: While trying to access database localhost/test2/root + using database-type POSTGRESQL: + Error database-destroy failed: dropdb: database removal failed: ERROR: database "test2" does not exist + has occurred. + [condition type: CLSQL-ACCESS-ERROR] + + + + Side Effects + A database will be removed from the filesystem of the host. + + + Exceptional Situations + An exception will be thrown if the database system does not + allow databases to be removed, the database does not exist, or + if database removal fails. + + + See Also + + + create-database + probe-database + list-databases + + + + + Notes + This function may invoke the operating systems + functions. Thus, some database systems may require the + administration functions to be available in the current + PATH. At this time, the + :mysql backend requires + mysqladmin and the + :postgresql backend requires + dropdb. + + destroy-database is a &clsql; extension. + + + + + + + PROBE-DATABASE + + + PROBE-DATABASE + tests for existence of a database + Function + + + Syntax + probe-database connection-spec &key database-type => success + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + success + + A boolean flag. If &t;, the database exists + in the database system. + + + + + + + Description + This function tests for the existence of a database in + the database system specified by + database-type. + + + + Examples + +(probe-database '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> T + + + + Side Effects + None + + + Exceptional Situations + An exception maybe thrown if the database system does + not receive administrator-level authentication since function + may need to read the administrative database of the database + system. + + + See Also + + + create-database + destroy-database + list-databases + + + + + Notes + + probe-database is a &clsql; extension. + + + + + + + LIST-DATABASES + + + LIST-DATABASES + List databases matching the supplied connection spec + and database type. + Function + + + Syntax + + list-databases connection-spec &key database-type => result + + + Arguments and Values + + + connection-spec + + A connection specification + + + + database-type + + A database type specifier, i.e. a keyword. + This defaults to the value of + *default-database-type* + + + + result + + A list of matching databases. + + + + + + + Description + + This function returns a list of databases existing in the + database system specified by + database-type. + + + + Examples + +(list-databases '("localhost" "new" "dent" "dent") :database-type :postgresql) +=> ("address-book" "sql-test" "template1" "template0" "test1" "dent" "test") + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + An exception maybe thrown if the database system does not + receive administrator-level authentication since function may + need to read the administrative database of the database + system. + + + + See Also + + + create-database + destroy-database + probe-database + + + + + Notes + + list-databases is a &clsql; extension. + + + + + + + + + + WITH-DATABASE + + + WITH-DATABASE + Execute a body of code with a variable bound to a + specified database object. + Macro + + + Syntax + + with-database db-var connection-spec &rest connect-args &body body => result + + + Arguments and Values + + + db-var + + A variable which is bound to the specified database. + + + + + connection-spec + + A vendor specific connection specification supplied + as a list or as a string. + + + + connect-args + + Other optional arguments to + connect. This macro use a value of + &nil; for connect's + make-default key, This is in + contrast to to the connect function which has a default + value of &t; for make-default. + + + + + body + + A Lisp code body. + + + + + result + + Determined by the result of executing the last + expression in body. + + + + + + + Description + Evaluate body in an environment, + where db-var is bound to the database + connection given by connection-spec and + connect-args. The connection is + automatically closed or released to the pool on exit from the + body. + + + + Examples + +(connected-databases) +=> NIL +(with-database (db '(":memory:") :database-type :sqlite + :make-default nil) + (database-name db)) +=> ":memory:" +(connected-databases) +=> NIL + + + + Side Effects + + See connect and disconnect. + + + + Affected by + + See connect and disconnect. + + + + Exceptional Situations + + See connect and disconnect. + + + + See Also + + + connect + disconnect + disconnect-pooled + with-default-database + + + + + Notes + + with-database is a &clsql; extension. + + + + + + + WITH-DEFAULT-DATABASE + + + WITH-DEFAULT-DATABASE + Execute a body of code with *default-database* bound to a specified database. + Macro + + + Syntax + + with-default-database database &rest body => result + + + Arguments and Values + + + database + + An active database object. + + + + + body + + A Lisp code body. + + + + + result + + Determined by the result of executing the last + expression in body. + + + + + + + Description + Perform body with + DATABASE bound as + *default-database*. + + + + Examples + +*default-database* +=> #<CLSQL-ODBC:ODBC-DATABASE new/dent OPEN {49095CAD}> + +(let ((database (clsql:find-database ":memory:"))) + (with-default-database (database) + (database-name *default-database*))) +=> ":memory:" + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + Calls to &clsql; functions in body may signal + errors if database is not an active database + object. + + + + See Also + + + with-database + *default-database* + + + + + Notes + + with-default-database is a &clsql; extension. + + + + + + diff --git a/doc/ref-fddl.xml b/doc/ref-fddl.xml new file mode 100644 index 0000000..b48bd3b --- /dev/null +++ b/doc/ref-fddl.xml @@ -0,0 +1,2618 @@ + + +%myents; +]> + + + + Functional Data Definition Language (FDDL) + + + &clsql; provides a functional DDL which supports the creation + and destruction of a variety of database objects including + tables, views, indexes and sequences. Functions which return + information about currently defined database objects are also + provided. In addition, the FDDL includes functionality for + examining table attributes and attribute types. + + + + + + CREATE-TABLE + + + CREATE-TABLE + Create a database table. + Function + + + Syntax + + create-table name description &key database constraints transactions => + + + Arguments and Values + + + name + + + The name of the table as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + description + + + A list. + + + + + constraints + + + A string, a list of strings or &nil;. + + + + + transactions + + + A Boolean. The default value is &t;. + + + + + + + Description + Creates a table called name, which + may be a string, symbol or SQL table identifier, in + database which defaults to + *default-database*. description + is a list whose elements are lists containing the attribute + names, types, and other constraints such as not-null or + primary-key for each column in the table. + + + constraints is a string representing an + SQL table constraint expression or a list of such strings. + + + With MySQL databases, if transactions + is &t; an InnoDB table is created which supports transactions. + + + + Examples + +(create-table [foo] + '(([id] integer) + ([height] float) + ([name] (string 24)) + ([comments] text))) +=> +(table-exists-p [foo]) +=> T + +(create-table [foo] '(([bar] integer :not-null :unique :primary-key) + ([baz] string :not-null :unique))) +=> +(table-exists-p [foo]) +=> T + +(create-table [foo] '(([bar] integer :not-null) ([baz] string :not-null)) + :constraints '("UNIQUE (bar,baz)" "PRIMARY KEY (bar)")) +=> +(table-exists-p [foo]) +=> T + + + + Side Effects + + A table is created in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if a + relation called name already exists. + + + + See Also + + drop-table + list-tables + table-exists-p + + + + Notes + + The constraints and + transactions keyword arguments to + create-table are &clsql; extensions. The + transactions keyword argument is for + compatibility with MySQL databases. + + + + + + + DROP-TABLE + + + DROP-TABLE + Drop a database table. + Function + + + Syntax + + drop-table name &key if-does-not-exist database => + + + Arguments and Values + + + name + + + The name of the table as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + if-does-not-exist + + + A symbol. Meaningful values are :ignore + or :error (the default). + + + + + + + Description + Drops the table called name from + database which defaults to + *default-database*. If the table does not exist + and if-does-not-exist is + :ignore then drop-table + returns &nil; whereas an error is signalled if + if-does-not-exist is + :error. + + + + Examples + +(table-exists-p [foo]) +=> T +(drop-table [foo] :if-does-not-exist :ignore) +=> +(table-exists-p [foo]) +=> NIL + + + + Side Effects + + A table is dropped database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if + name doesn't exist and + if-does-not-exist has a value of + :error. + + + + See Also + + create-table + list-tables + table-exists-p + + + + Notes + + The if-does-not-exist keyword argument + to drop-table is a &clsql; extension. + + + + + + + LIST-TABLES + + + LIST-TABLES + Returns a list of database tables. + Function + + + Syntax + + list-tables &key owner database => result + + + Arguments and Values + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A list of strings. + + + + + + + Description + Returns a list of strings representing table names in + database which defaults to + *default-database*. owner + is &nil; by default which means that only tables owned by users + are listed. If owner is a string denoting + a user name, only tables owned by owner + are listed. If owner is + :all then all tables are listed. + + + + Examples + +(list-tables :owner "fred") +=> ("type_table" "type_bigint" "employee" "company" "addr" "ea_join" "big") + +(list-tables :owner :all) +=> ("pg_description" "pg_group" "pg_proc" "pg_rewrite" "pg_type" "pg_attribute" + "pg_class" "pg_inherits" "pg_index" "pg_operator" "pg_opclass" "pg_am" + "pg_amop" "pg_amproc" "pg_language" "pg_largeobject" "pg_aggregate" + "pg_trigger" "pg_listener" "pg_cast" "pg_namespace" "pg_shadow" + "pg_conversion" "pg_depend" "pg_attrdef" "pg_constraint" "pg_database" + "type_table" "type_bigint" "employee" "company" "pg_statistic" "addr" + "ea_join" "big") + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-table + drop-table + table-exists-p + + + + Notes + + None. + + + + + + + TABLE-EXISTS-P + + + TABLE-EXISTS-P + Tests for the existence of a database table. + Function + + + Syntax + + table-exists-p name &key owner database => result + + + Arguments and Values + + + name + + + The name of the table as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A Boolean. + + + + + + + Description + Tests for the existence of an SQL table called + name in database + which defaults to *default-database*. + owner is &nil; by default which means + that only tables owned by users are examined. If + owner is a string denoting a user name, + only tables owned by owner are + examined. If owner is + :all then all tables are examined. + + + + Examples + +(table-exists-p [foo]) +=> T + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-table + drop-table + list-tables + + + + Notes + + None. + + + + + + + CREATE-VIEW + + + CREATE-VIEW + Create a database view. + Function + + + Syntax + + create-view name &key as column-list with-check-option database => + + + Arguments and Values + + + name + + + The name of the view as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + as + + + A symbolic SQL query expression. + + + + + column-list + + + A list. + + + + + with-check-option + + + A Boolean. + + + + + + + Description + Creates a view called name in + database which defaults to + *default-database*. The view is created using + the query as and the columns of the view + may be specified using the column-list + parameter. The with-check-option is &nil; + by default but if it has a non-&nil; value, then all + insert/update commands on the view are checked to ensure that + the new data satisfy the query as. + + + + Examples + +(create-view [lenins-group] + :as [select [first-name] [last-name] [email] + :from [employee] + :where [= [managerid] 1]]) +=> + +(select [*] :from [lenins-group]) +=> (("Josef" "Stalin" "stalin@soviet.org") + ("Leon" "Trotsky" "trotsky@soviet.org") + ("Nikita" "Kruschev" "kruschev@soviet.org") + ("Leonid" "Brezhnev" "brezhnev@soviet.org") + ("Yuri" "Andropov" "andropov@soviet.org") + ("Konstantin" "Chernenko" "chernenko@soviet.org") + ("Mikhail" "Gorbachev" "gorbachev@soviet.org") + ("Boris" "Yeltsin" "yeltsin@soviet.org") + ("Vladimir" "Putin" "putin@soviet.org")), + ("first_name" "last_name" "email") + + + + Side Effects + + A view is created in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if a + relation called name already exists. + + + + See Also + + drop-view + list-views + view-exists-p + + + + Notes + + None. + + + + + + + DROP-VIEW + + + DROP-VIEW + Drops a database view. + Function + + + Syntax + + drop-view name &key if-does-not-exist database => + + + Arguments and Values + + + name + + + The name of the view as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + if-does-not-exist + + + A symbol. Meaningful values are :ignore + or :error (the default). + + + + + + + Description + Drops the view called name from + database which defaults to + *default-database*. If the view does not exist + and if-does-not-exist is + :ignore then drop-view + returns &nil; whereas an error is signalled if + if-does-not-exist is + :error. + + + + Examples + +(view-exists-p [foo]) +=> T +(drop-view [foo] :if-does-not-exist :ignore) +=> +(view-exists-p [foo]) +=> NIL + + + + Side Effects + + A view is dropped database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if + name doesn't exist and + if-does-not-exist has a value of + :error. + + + + See Also + + create-view + list-views + view-exists-p + + + + Notes + + The if-does-not-exist keyword argument + to drop-view is a &clsql; extension. + + + + + + + LIST-VIEWS + + + LIST-VIEWS + Returns a list of database views. + Function + + + Syntax + + list-views &key owner database => result + + + Arguments and Values + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A list of strings. + + + + + + + Description + Returns a list of strings representing view names in + database which defaults to + *default-database*. owner + is &nil; by default which means that only views owned by users + are listed. If owner is a string denoting + a user name, only views owned by owner + are listed. If owner is + :all then all views are listed. + + + + Examples + +(list-views :owner "fred") +=> ("lenins_group") + +(list-views :owner :all) +=> ("pg_user" "pg_rules" "pg_views" "pg_tables" "pg_indexes" "pg_stats" + "pg_stat_all_tables" "pg_stat_sys_tables" "pg_stat_user_tables" + "pg_statio_all_tables" "pg_statio_sys_tables" "pg_statio_user_tables" + "pg_stat_all_indexes" "pg_stat_sys_indexes" "pg_stat_user_indexes" + "pg_statio_all_indexes" "pg_statio_sys_indexes" "pg_statio_user_indexes" + "pg_statio_all_sequences" "pg_statio_sys_sequences" + "pg_statio_user_sequences" "pg_stat_activity" "pg_stat_database" + "pg_locks" "pg_settings" "lenins_group") + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-view + drop-view + view-exists-p + + + + Notes + + list-views is a &clsql; extension. + + + + + + + VIEW-EXISTS-P + + + VIEW-EXISTS-P + Tests for the existence of a database view. + Function + + + Syntax + + view-exists-p name &key owner database => result + + + Arguments and Values + + + name + + + The name of the view as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A Boolean. + + + + + + + Description + Tests for the existence of an SQL view called + name in database + which defaults to *default-database*. + owner is &nil; by default which means + that only views owned by users are examined. If + owner is a string denoting a user name, + only views owned by owner are + examined. If owner is + :all then all views are examined. + + + + Examples + +(view-exists-p [lenins-group]) +=> T + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-view + drop-view + list-views + + + + Notes + + view-exists-p is a &clsql; extension. + + + + + + + CREATE-INDEX + + + CREATE-INDEX + Create a database index. + Function + + + Syntax + + create-index name &key on unique attributes database => + + + Arguments and Values + + + name + + + The name of the index as a string, symbol or SQL expression. + + + + + on + + + The name of a table as a string, symbol or SQL expression. + + + + + unique + + + A Boolean. + + + + + attributes + + + A list of attribute names. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + + + Description + Creates an index called name on the + table specified by on in + database which default to + *default-database*. The table attributes to use + in constructing the index name are + specified by attributes. The + unique argument is &nil; by default but + if it has a non-&nil; value then the indexed attributes must + have unique values. + + + + Examples + +(create-index [bar] :on [employee] + :attributes '([first-name] [last-name] [email]) + :unique t) +=> + +(index-exists-p [bar]) +=> T + + + + Side Effects + + An index is created in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if a + relation called name already exists. + + + + See Also + + drop-index + list-indexes + index-exists-p + + + + Notes + + None. + + + + + + + DROP-INDEX + + + DROP-INDEX + Drop a database index. + Function + + + Syntax + + drop-index name &key if-does-not-exist on database => + + + Arguments and Values + + + name + + + The name of the index as a string, symbol or SQL expression. + + + + + on + + + The name of a table as a string, symbol or SQL + expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + if-does-not-exist + + + A symbol. Meaningful values are :ignore + or :error (the default). + + + + + + + Description + Drops the index called name in + database which defaults to + *default-database*. If the index does not exist + and if-does-not-exist is + :ignore then drop-index + returns &nil; whereas an error is signalled if + if-does-not-exist is + :error. + + + The argument on allows the optional + specification of a table to drop the index from. This is + required for compatability with MySQL. + + + + Examples + +(index-exists-p [foo]) +=> T +(drop-index [foo] :if-does-not-exist :ignore) +=> +(index-exists-p [foo]) +=> NIL + + + + Side Effects + + An index is dropped in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if + name doesn't exist and + if-does-not-exist has a value of + :error. + + + + See Also + + create-index + list-indexes + index-exists-p + + + + Notes + + The if-does-not-exist and + on keyword arguments to + drop-index are &clsql; extensions. The + keyword argument on is provided for + compatibility with MySQL. + + + + + + + LIST-INDEXES + + + LIST-INDEXES + Returns a list of database indexes. + Function + + + Syntax + + list-indexes &key onowner database => result + + + Arguments and Values + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + on + + + The name of a table as a string, symbol or SQL + expression, a list of such names or &nil;. + + + + + result + + + A list of strings. + + + + + + + Description + Returns a list of strings representing index names in + database which defaults to + *default-database*. owner + is &nil; by default which means that only indexes owned by users + are listed. If owner is a string denoting + a user name, only indexes owned by owner + are listed. If owner is + :all then all indexes are listed. + + + The keyword argument on limits the + results to indexes on the specified tables. Meaningful values + for on are &nil; (the default) which + means that all tables are considered, a string, symbol or SQL + expression representing a table name in + database or a list of such table + identifiers. + + + + Examples + +(list-indexes) +=> ("employeepk" "companypk" "addrpk" "bar") + +(list-indexes :on '([addr] [company])) +=> ("addrpk" "companypk") + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-index + drop-index + index-exists-p + + + + Notes + + list-indexes is a &clsql; extension. + + + + + + + INDEX-EXISTS-P + + + INDEX-EXISTS- + Tests for the existence of a database index. + Function + + + Syntax + + index-exists-p name &key owner database => result + + + Arguments and Values + + + name + + + The name of the index as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A Boolean. + + + + + + + Description + Tests for the existence of an SQL index called + name in database + which defaults to + *default-database*. owner + is &nil; by default which means that only indexes owned by users + are examined. If owner is a string + denoting a user name, only indexes owned by + owner are examined. If + owner is :all then all + indexes are examined. + + + + Examples + +(index-exists-p [bar]) +=> T + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-index + drop-index + list-indexes + + + + Notes + + index-exists-p is a &clsql; extension. + + + + + + + ATTRIBUTE-TYPE + + + ATTRIBUTE-TYPE + Returns the type of the supplied attribute. + Function + + + Syntax + + attribute-type attribute table &key owner database => type, precision, scale, nulls-accepted + + + Arguments and Values + + + attribute + + + The name of the index as a string, symbol or SQL expression. + + + + + table + + + The name of a table as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + type + + + A keyword symbol denoting a vendor-specific SQL type. + + + + + precision + + + An integer denoting the precision of the attribute type + or &nil;. + + + + + scale + + + An integer denoting the scale of the attribute type + or &nil;. + + + + + nulls-accepted + + + 0 or 1. + + + + + + + Description + Returns a keyword symbol representing the vendor-specific + field type of the supplied attribute + attribute in the table specified by + table in database + which defaults to + *default-database*. owner + is &nil; by default which means that the attribute specified by + attribute, if it exists, must be user + owned else &nil; is returned. If owner is + a string denoting a user name, the attribute, if it exists, must + be owned by owner else &nil; is returned, + whereas if owner is :all + then the attribute, if it exists, will be returned regardless of + its owner. + + + Other information is also returned. The second value is + the type precision, the third is the scale and the fourth + represents whether or not the attribute accepts null values (a + value of 0) or not (a value of 1). + + + + Examples + +(attribute-type [emplid] [employee]) +=> :INT4, 4, NIL, 0 + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + list-attributes + list-attribute-types + + + + Notes + + None. + + + + + + + LIST-ATTRIBUTE-TYPES + + + LIST-ATTRIBUTE-TYPES + Returns information about the attribute types of a table. + Function + + + Syntax + + list-attribute-types table &key owner database => result + + + Arguments and Values + + + table + + + The name of a table as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A list. + + + + + + + Description + Returns a list containing information about the SQL types + of each of the attributes in the table specified by + table in database + which has a default value of + *default-database*. owner + is &nil; by default which means that only attributes owned by + users are listed. If owner is a string + denoting a user name, only attributes owned by + owner are listed. If + owner is :all then all + attributes are listed. The elements of the returned list are + lists where the first element is the name of the attribute, the + second element is its SQL type, the third is the type precision, + the fourth is the scale of the attribute and the fifth is 1 if + the attribute accepts null values and otherwise 0. + + + + Examples + +(list-attribute-types [employee]) +=> (("emplid" :INT4 4 NIL 0) ("groupid" :INT4 4 NIL 0) + ("first_name" :VARCHAR 30 NIL 1) ("last_name" :VARCHAR 30 NIL 1) + ("email" :VARCHAR 100 NIL 1) ("ecompanyid" :INT4 4 NIL 1) + ("managerid" :INT4 4 NIL 1) ("height" :FLOAT8 8 NIL 1) + ("married" :BOOL 1 NIL 1) ("birthday" :TIMESTAMP 8 NIL 1) + ("bd_utime" :INT8 8 NIL 1)) + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + attribute-type + list-attribute-types + + + + Notes + + None. + + + + + + + LIST-ATTRIBUTES + + + LIST-ATTRIBUTES + Returns the attributes of a table as a list. + Function + + + Syntax + + list-attributes name &key owner database => result + + + Arguments and Values + + + name + + + The name of a table as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A list. + + + + + + + Description + Returns a list of strings representing the attributes of + table name in + database which defaults to + *default-database*. owner + is &nil; by default which means that only attributes owned by + users are listed. If owner is a string + denoting a user name, only attributes owned by + owner are listed. If + owner is :all then all + attributes are listed. + + + + Examples + +(list-attributes [employee]) +=> ("emplid" "groupid" "first_name" "last_name" "email" "ecompanyid" "managerid" + "height" "married" "birthday" "bd_utime") + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + attribute-type + list-attribute-types + + + + Notes + + None. + + + + + + + CREATE-SEQUENCE + + + CREATE-SEQUENCE + Create a database sequence. + Function + + + Syntax + + create-sequence name &key database => + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + + + Description + Creates a sequence called name in + database which defaults to + *default-database*. + + + + Examples + +(create-sequence [foo]) +=> +(sequence-exists-p [foo]) +=> T + + + + Side Effects + + A sequence is created in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if a + relation called name already exists. + + + + See Also + + drop-sequence + list-sequences + sequence-exists-p + sequence-last + sequence-next + set-sequence-position + + + + Notes + + create-sequence is a &clsql; extension. + + + + + + + DROP-SEQUENCE + + + DROP-SEQUENCE + Drop a database sequence. + Function + + + Syntax + + drop-sequence name &key if-does-not-exist database => + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + if-does-not-exist + + + A symbol. Meaningful values are :ignore + or :error (the default). + + + + + + + Description + Drops the sequence called name from + database which defaults to + *default-database*. If the sequence does not + exist and if-does-not-exist is + :ignore then + drop-sequence returns &nil; whereas an + error is signalled if if-does-not-exist + is :error. + + + + Examples + +(sequence-exists-p [foo]) +=> T +(drop-sequence [foo] :if-does-not-exist :ignore) +=> +(sequence-exists-p [foo]) +=> NIL + + + + Side Effects + + A sequence is dropped from database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + An error is signalled if name is not a + string, symbol or SQL expression. An error of type + sql-database-data-error is signalled if + name doesn't exist and + if-does-not-exist has a value of + :error. + + + + See Also + + create-sequence + list-sequences + sequence-exists-p + sequence-last + sequence-next + set-sequence-position + + + + Notes + + drop-sequence is a &clsql; extension. + + + + + + + LIST-SEQUENCES + + + LIST-SEQUENCES + Returns a list of database sequences. + Function + + + Syntax + + list-sequences &key owner database => result + + + Arguments and Values + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A list of strings. + + + + + + + Description + Returns a list of strings representing sequence names in + database which defaults to + *default-database*. owner + is &nil; by default which means that only sequences owned by + users are listed. If owner is a string + denoting a user name, only sequences owned by + owner are listed. If + owner is :all then all + sequences are listed. + + + + Examples + +(list-sequences) +=> ("foo") + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-sequence + drop-sequence + sequence-exists-p + sequence-last + sequence-next + set-sequence-position + + + + Notes + + list-sequences is a &clsql; extension. + + + + + + + SEQUENCE-EXISTS-P + + + SEQUENCE-EXISTS-P + Tests for the existence of a database sequence. + Function + + + Syntax + + sequence-exists-p name &key owner database => result + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + owner + + + A string, &nil; or :all. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + A Boolean. + + + + + + + Description + Tests for the existence of an SQL sequence called + name in database + which defaults to + *default-database*. owner + is &nil; by default which means that only sequences owned by + users are examined. If owner is a string + denoting a user name, only sequences owned by + owner are examined. If + owner is :all then all + sequences are examined. + + + + Examples + +(sequence-exists-p [foo]) +=> NIL + + + + Side Effects + + None. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + None. + + + + See Also + + create-sequence + drop-sequence + list-sequences + sequence-last + sequence-next + set-sequence-position + + + + Notes + + sequence-exists-p is a &clsql; extension. + + + + + + + SEQUENCE-LAST + + + SEQUENCE-LAST + Return the last element in a database sequence. + Function + + + Syntax + + sequence-last name &key database => result + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + An integer. + + + + + + + Description + Return the last value allocated in the sequence called + name in database + which defaults to *default-database*. + + + + Examples + +(sequence-last [foo]) +=> 1 + + + + Side Effects + + None. + + + + Affected by + + The current value stored in database sequence + name. + + + *default-database* + + + + Exceptional Situations + + Will signal an error of type + sql-database-data-error if a sequence called + name does not exist in + database. + + + + See Also + + create-sequence + drop-sequence + list-sequences + sequence-exists-p + sequence-next + set-sequence-position + + + + Notes + + sequence-last is a &clsql; extension. + + + + + + + SEQUENCE-NEXT + + + SEQUENCE-NEXT + Increment the value of a database sequence. + Function + + + Syntax + + sequence-next name &key database => result + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + An integer. + + + + + + + Description + Increment and return the value of the sequence called + name in database + which defaults to *default-database*. + + + + Examples + +(sequence-last [foo]) +=> 3 +(sequence-next [foo]) +=> 4 +(sequence-next [foo]) +=> 5 +(sequence-next [foo]) +=> 6 + + + + Side Effects + + Modifies the value of the sequence name + in database. + + + + Affected by + + The current value stored in database sequence + name. + + + *default-database* + + + + Exceptional Situations + + Will signal an error of type + sql-database-data-error if a sequence called + name does not exist in + database. + + + + See Also + + create-sequence + drop-sequence + list-sequences + sequence-exists-p + sequence-last + set-sequence-position + + + + Notes + + sequence-next is a &clsql; extension. + + + + + + + SET-SEQUENCE-POSITION + + + SET-SEQUENCE-POSITION + Sets the position of a database sequence. + Function + + + Syntax + + set-sequence-position name position &key database => result + + + Arguments and Values + + + name + + + The name of the sequence as a string, symbol or SQL expression. + + + + + position + + + An integer. + + + + + database + + + A database object which defaults to + *default-database*. + + + + + result + + + An integer. + + + + + + + Description + Explicitly set the position of the sequence called + name in database, + which defaults to *default-database*, to + position which is returned. + + + + Examples + +(sequence-last [foo]) +=> 4 +(set-sequence-position [foo] 50) +=> 50 +(sequence-next [foo]) +=> 51 + + + + Side Effects + + Modifies the value of the sequence name + in database. + + + + Affected by + + *default-database* + + + + Exceptional Situations + + Will signal an error of type + sql-database-data-error if a sequence called + name does not exist in + database. + + + + See Also + + create-sequence + drop-sequence + list-sequences + sequence-exists-p + sequence-last + sequence-next + + + + Notes + + set-sequence-position is a &clsql; extension. + + + + + + + TRUNCATE-DATABASE + + + TRUNCATE-DATABASE + Drop all tables, views, indexes and sequences in a database. + Function + + + Syntax + + truncate-database &key database => + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + + Drop all tables, views, indexes and sequences in + database which defaults to + *default-database*. + + + + Examples + +(list-tables) +=> ("type_table" "type_bigint" "employee" "company" "addr" "ea_join" "big") +(list-indexes) +=> ("employeepk" "companypk" "addrpk") +(list-views) +=> ("lenins_group") +(list-sequences) +=> ("foo" "bar") +(truncate-database) +=> +(list-tables) +=> NIL +(list-indexes) +=> NIL +(list-views) +=> NIL +(list-sequences) +=> NIL + + + + Side Effects + + Modifications are made to the underlying database. + + + + Affected by + + None. + + + + Exceptional Situations + + Signals an error of type sql-database-error if + database is not a database object. + + + + See Also + + + drop-table + drop-view + drop-index + drop-sequence + + + + + Notes + + truncate-database is a &clsql; extension. + + + + + diff --git a/doc/ref-fdml.xml b/doc/ref-fdml.xml new file mode 100644 index 0000000..7113b6a --- /dev/null +++ b/doc/ref-fdml.xml @@ -0,0 +1,2116 @@ + + +%myents; +]> + + + + Functional Data Manipulation Language (FDML) + + + The functional data manipulation interface provided by &clsql; + includes functions for inserting, updating and deleting records + in existing database tables and executing SQL queries and + statements with the results of queries returned as Lisp types. + SQL statements expressed as strings may be executed with the + query and + execute-command + functions. The select function, on + the other hand, allows for the construction of queries in Lisp + using the symbolic SQL syntax. Finally, iterative manipulation + of query results is supported by do-query, map-query and an + extended clause for the loop macro. + + + + + + + + *CACHE-TABLE-QUERIES-DEFAULT* + + + *CACHE-TABLE-QUERIES-DEFAULT* + Specifies the default behaviour for caching of + attribute types. + Variable + + + Value Type + + A valid argument to the action + parameter of cache-table-queries, + i.e. one of + + &t; + &nil; + :flush + . + + + + Initial Value + nil + + + Description + + Specifies the default behaivour for caching of attribute + types. Meaningful values are &t;, &nil; and + :flush as described for the + action argument to + cache-table-queries. + + + + Examples + None. + + + Affected By + None. + + + See Also + + cache-table-queries + + + + Notes + None. + + + + + + CACHE-TABLE-QUERIES + + + CACHE-TABLE-QUERIES + Control the caching of table attribute types. + Function + + + Syntax + + cache-table-queries table &key action database => + + + Arguments and Values + + + table + + + A string representing a database table, &t; or + :default. + + + + + action + + + &t;, &nil; or :flush. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + Controls the caching of attribute type information on the + table specified by table in + database which defaults to + *default-database*. action + specifies the caching behaviour to adopt. If its value is &t; + then attribute type information is cached whereas if its value + is &nil; then attribute type information is not cached. If + action is :flush then + all existing type information in the cache for + table is removed, but caching is still + enabled. table may be a string + representing a table for which the caching action is to be taken + while the caching action is applied to all tables if + table is &t;. Alternatively, when + table is :default, the + default caching action specified by + *cache-table-queries-default* is applied to all + tables for which a caching action has not been explicitly set. + + + + Examples + +(setf *cache-table-queries-default* t) +=> T +(create-table [foo] + '(([id] integer) + ([height] float) + ([name] (string 24)) + ([comments] varchar))) +=> +(cache-table-queries "foo") +=> +(list-attribute-types "foo") +=> (("id" :INT4 4 NIL 1) ("height" :FLOAT8 8 NIL 1) ("name" :BPCHAR 24 NIL 1) + ("comments" :VARCHAR 255 NIL 1)) +(drop-table "foo") +=> +(create-table [foo] + '(([id] integer) + ([height] float) + ([name] (string 36)) + ([comments] (string 100)))) +=> +(cache-table-queries "foo" :action :flush) +=> +(list-attribute-types "foo") +=> (("id" :INT4 4 NIL 1) ("height" :FLOAT8 8 NIL 1) ("name" :BPCHAR 36 NIL 1) + ("comments" :BPCHAR 100 NIL 1)) + + + + Side Effects + + The internal attribute cache for + database is modified. + + + + Affected by + + *cache-table-queries-default* + + + + Exceptional Situations + + None. + + + + See Also + + *cache-table-queries-default* + + + + Notes + + None. + + + + + + + INSERT-RECORDS + + + INSERT-RECORDS + Insert tuples of data into a database table. + Function + + + Syntax + + insert-records &key into attributes values av-pairs query database => + + + Arguments and Values + + + into + + + A string, symbol or symbolic SQL expression representing + the name of a table existing in + database. + + + + + attributes + + + A list of attribute identifiers or &nil;. + + + + + values + + + A list of attribute values or &nil;. + + + + + av-pairs + + + A list of attribute identifier/value pairs or &nil;. + + + + + query + + + A query expression or &nil;. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + + Inserts records into the table specified by + into in database + which defaults to *default-database*. + + + There are five ways of specifying the values inserted into + each row. In the first values contains + a list of values to insert and + attributes, + av-pairs and + query are &nil;. This can be used when + values are supplied for all attributes in + into. In the second, + attributes is a list of column names, + values is a corresponding list of + values and av-pairs and + query are &nil;. In the third, + attributes, + values and query + are &nil; and av-pairs is an alist of + (attribute value) pairs. In the fourth, + values, av-pairs + and attributes are &nil; and + query is a symbolic SQL query + expression in which the selected columns also exist in + into. In the fifth method, + values and + av-pairs are nil and + attributes is a list of column names + and query is a symbolic SQL query + expression which returns values for the specified columns. + + + + Examples + +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 11] + :field-names nil) +=> NIL +(insert-records :into [employee] + :attributes '(emplid groupid first_name last_name email + ecompanyid managerid) + :values '(11 1 "Yuri" "Gagarin" "gagarin@soviet.org" + 1 1)) +=> +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 11] + :field-names nil) +=> (("Yuri" "Gagarin" "gagarin@soviet.org")) + + + + Side Effects + + Modifications are made to the underlying database. + + + + Affected by + + None. + + + + Exceptional Situations + + An error of type sql-database-data-error is + signalled if table is not an existing + table in database or if the specified + attributes are not found. + + + + See Also + + update-records + delete-records + + + + Notes + + None. + + + + + + + UPDATE-RECORDS + + + UPDATE-RECORDS + Updates the values of existing records. + Function + + + Syntax + + update-records table &key attributes values av-pairs where database => + + + Arguments and Values + + + table + + + A string, symbol or symbolic SQL expression representing + the name of a table existing in + database. + + + + + attributes + + + A list of attribute identifiers or &nil;. + + + + + values + + + A list of attribute values or &nil;. + + + + + av-pairs + + + A list of attribute identifier/value pairs or &nil;. + + + + + where + + + A symbolic SQL expression. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + + Updates the attribute values of existing records satsifying + the SQL expression where in the table + specified by table in + database which defaults to + *default-database*. + + + There are three ways of specifying the values to update for + each row. In the first, values contains + a list of values to use in the update and + attributes and + av-pairs are &nil;. This can be used + when values are supplied for all attributes in + table. In the second, + attributes is a list of column names, + values is a corresponding list of + values and av-pairs is &nil;. In the + third, attributes and + values are &nil; and + av-pairs is an alist of (attribute + value) pairs. + + + + Examples + +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 1] + :field-names nil) +=> (("Vladimir" "Lenin" "lenin@soviet.org")) +(update-records [employee] + :av-pairs'((first_name "Yuri") + (last_name "Gagarin") + (email "gagarin@soviet.org")) + :where [= [emplid] 1]) +=> +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 1] + :field-names nil) +=> (("Yuri" "Gagarin" "gagarin@soviet.org")) + + + + Side Effects + + Modifications are made to the underlying database. + + + + Affected by + + None. + + + + Exceptional Situations + + An error of type sql-database-data-error is + signalled if table is not an existing + table in database, if the specified + attributes are not found or if the SQL statement resulting + from the symbolic expression where does + not return a Boolean value. + + If the execution of the SQL query leads to any errors, an + error of type sql-database-error is + signalled. + + + See Also + + + insert-records + delete-records + + + + + Notes + + None. + + + + + + + DELETE-RECORDS + + + DELETE-RECORDS + Delete records from a database table. + Function + + + Syntax + + delete-records &key from where database => + + + Arguments and Values + + + from + + + A string, symbol or symbolic SQL expression representing + the name of a table existing in + database. + + + + + where + + + A symbolic SQL expression. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + Deletes records satisfying the SQL expression + where from the table specified by + from in database + specifies a database which defaults to + *default-database*. + + + + Examples + +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 11] + :field-names nil) +=> (("Yuri" "Gagarin" "gagarin@soviet.org")) +(delete-records :from [employee] :where [= [emplid] 11]) +=> +(select [first-name] [last-name] [email] + :from [employee] + :where [= [emplid] 11] + :field-names nil) +=> NIL + + + + Side Effects + + Modifications are made to the underlying database. + + + + Affected by + + None. + + + + Exceptional Situations + + An error of type sql-database-data-error is + signalled if from is not an existing + table in database or if the SQL + statement resulting from the symbolic expression + where does not return a Boolean value. + + + + See Also + + + insert-records + update-records + + + + + Notes + + None. + + + + + + + + + + EXECUTE-COMMAND + + + EXECUTE-COMMAND + Execute an SQL command which returns no values. + Generic Function + + + Syntax + + execute-command sql-expression &key database => + + + Arguments and Values + + + sql-expression + + An sql + expression that represents an SQL + statement which will return no values. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + Description + Executes the SQL command + sql-expression, which may be a symbolic + SQL expression or a string representing any SQL statement apart + from a query, on the supplied database + which defaults to *default-database*. + + + + Examples + + (execute-command "create table eventlog (time char(30),event char(70))") + => + + (execute-command "create table eventlog (time char(30),event char(70))") + >> + >> While accessing database #<CLSQL-POSTGRESQL:POSTGRESQL-DATABASE {480B2B6D}> + >> with expression "create table eventlog (time char(30),event char(70))": + >> Error NIL: ERROR: amcreate: eventlog relation already exists + >> has occurred. + >> + >> Restarts: + >> 0: [ABORT] Return to Top-Level. + >> + >> Debug (type H for help) + >> + >> (CLSQL-POSTGRESQL::|(PCL::FAST-METHOD DATABASE-EXECUTE-COMMAND (T POSTGRESQL-DATABASE))| + >> #<unused-arg> + >> #<unused-arg> + >> #<unavailable-arg> + >> #<unavailable-arg>) + >> Source: (ERROR 'SQL-DATABASE-ERROR :DATABASE DATABASE :EXPRESSION ...) + >> 0] 0 + + (execute-command "drop table eventlog") + => + + + + Side Effects + Whatever effects the execution of the SQL statement has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL statement leads to any errors, + an error of type sql-database-error is + signalled. + + + See Also + + + query + + + + + Notes + None. + + + + + + + QUERY + + + QUERY + Execute an SQL query and return the tuples as a + list. + Generic Function + + + Syntax + + query query-expression &key database result-types flatp field-names => result + + + Arguments and Values + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + flatp + + A Boolean whose default value is &nil;. + + + + result-types + + A + field type + specifier. The default is :auto;. + + + The purpose of this argument is cause &clsql; to + import SQL numeric fields into numeric Lisp objects + rather than strings. This reduces the cost of + allocating a temporary string and the &clsql; users' + inconvenience of converting number strings into number + objects. + + + A value of :auto causes &clsql; + to automatically convert SQL fields into a + numeric format where applicable. The default value of + &nil; causes all fields to be returned as strings + regardless of the SQL type. Otherwise a list is expected + which has a element for each field that specifies the + conversion. Valid type identifiers are: + + :int Field is imported as a + signed integer, from 8-bits to 64-bits depending + upon the field type. + + :double Field is imported as a + double-float number. + + t Field is imported as a + string. + + + If the list is shorter than the number of fields, the a + value of t is assumed for the field. + If the list is longer than the number of fields, the + extra elements are ignored. + + + + + field-names + + + A boolean with a default value of &t;. When &t;, this + function returns a second value of a list of field + names. When &nil;, this function only returns one value - + the list of rows. + + + + + result + + A list representing the result set obtained. For + each tuple in the result set, there is an element in + this list, which is itself a list of all the attribute + values in the tuple. + + + + + + Description + + Executes the SQL query expression + query-expression, which may be an SQL + expression or a string, on the supplied + database which defaults to + *default-database*. result-types + is a list of symbols which specifies the lisp type for each + field returned by query-expression. + + + If result-types is &nil; all results + are returned as strings whereas the default value of + :auto means that the lisp types are + automatically computed for each field. + + + field-names is &t; by default which + means that the second value returned is a list of strings + representing the columns selected by + query-expression. If + field-names is &nil;, the list of column + names is not returned as a second value. + + + flatp has a default value of &nil; + which means that the results are returned as a list of + lists.If FLATP is &t; and only one result is returned for each + record selected by query-expression, + the results are returned as elements of a list. + + + + Examples + +(query "select emplid,first_name,last_name,height from employee where emplid = 1") +=> ((1 "Vladimir" "Lenin" 1.5564661d0)), + ("emplid" "first_name" "last_name" "height") + +(query "select emplid,first_name,last_name,height from employee where emplid = 1" + :field-names nil) +=> ((1 "Vladimir" "Lenin" 1.5564661d0)) + +(query "select emplid,first_name,last_name,height from employee where emplid = 1" + :field-names nil + :result-types nil) +=> (("1" "Vladimir" "Lenin" "1.5564661")) + +(query "select emplid,first_name,last_name,height from employee where emplid = 1" + :field-names nil + :result-types '(:int t t :double)) +=> ((1 "Vladimir" "Lenin" 1.5564661)) + +(query "select last_name from employee where emplid > 5" :flatp t) +=> ("Andropov" "Chernenko" "Gorbachev" "Yeltsin" "Putin"), + ("last_name") + +(query "select last_name from employee where emplid > 10" + :flatp t + :field-names nil) +=> NIL + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any errors, an + error of type sql-database-error is + signalled. + + + See Also + + execute-command + print-query + do-query + map-query + loop + select + + + + Notes + The field-names and + result-types keyword arguments are a + &clsql; extension. + + + + + + PRINT-QUERY + + + PRINT-QUERY + Prints a tabular report of query results. + Function + + + Syntax + + print-query query-expression &key titles formats sizes stream database => + + + Arguments and Values + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + titles + + + A list of strings or &nil; which is the default value. + + + + + formats + + + A list of strings, &nil; or &t; which is the default value. + + + + + sizes + + + A list of numbers, &nil; or &t; which is the default value. + + + + + stream + + + An output stream or &t; which is the default value. + + + + + + + Description + Prints a tabular report of the results returned by the SQL + query query-expression, which may be a + symbolic SQL expression or a string, in + database which defaults to + *default-database*. The report is printed onto + stream which has a default value of &t; + which means that *standard-output* is used. The + title argument, which defaults to &nil;, + allows the specification of a list of strings to use as column + titles in the tabular output. sizes + accepts a list of column sizes, one for each column selected by + query-expression, to use in formatting + the tabular report. The default value of &t; means that minimum + sizes are computed. formats is a list of + format strings to be used for printing each column selected by + query-expression. The default value of + formats is &t; meaning that + ~A is used to format all columns or + ~VA if column sizes are used. + + + + Examples + +(print-query [select [emplid] [first-name] [last-name] [email] + :from [employee] + :where [< [emplid] 5]] + :titles '("ID" "FORENAME" "SURNAME" "EMAIL")) +ID FORENAME SURNAME EMAIL +1 Vladimir Lenin lenin@soviet.org +2 Josef Stalin stalin@soviet.org +3 Leon Trotsky trotsky@soviet.org +4 Nikita Kruschev kruschev@soviet.org +=> + +(print-query "select emplid,first_name,last_name,email from employee where emplid >= 5" + :titles '("ID" "FORENAME" "SURNAME" "EMAIL")) +ID FORENAME SURNAME EMAIL +5 Leonid Brezhnev brezhnev@soviet.org +6 Yuri Andropov andropov@soviet.org +7 Konstantin Chernenko chernenko@soviet.org +8 Mikhail Gorbachev gorbachev@soviet.org +9 Boris Yeltsin yeltsin@soviet.org +10 Vladimir Putin putin@soviet.org +=> + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + If the execution of the SQL query leads to any errors, an + error of type sql-database-error is + signalled. + + + + See Also + + query + do-query + map-query + loop + select + + + + Notes + + None. + + + + + + + SELECT + + + SELECT + Executes a query given the supplied constraints. + Function + + + Syntax + + select &rest identifiers &key all distinct from group-by having limit offset order-by set-operation where result-types field-names flatp refresh caching database => result + + + Arguments and Values + + + identifiers + + + A set of sql + expressions each of which indicates a column + to query. + + + + + all + + + A Boolean. + + + + + distinct + + + A Boolean. + + + + + from + + + One or more SQL expression representing tables. + + + + + group-by + + + An SQL expression. + + + + + having + + + An SQL expression. + + + + + limit + + + A non-negative integer. + + + + + offset + + + A non-negative integer. + + + + + order-by + + + An SQL expression. + + + + + set-operation + + + An SQL expression. + + + + + where + + + An SQL expression. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + flatp + + A Boolean whose default value is &nil;. + + + + result-types + + A + field type + specifier. The default is :auto. + + + The purpose of this argument is cause &clsql; to + import SQL numeric fields into numeric Lisp objects + rather than strings. This reduces the cost of + allocating a temporary string and the &clsql; users' + inconvenience of converting number strings into number + objects. + + + A value of :auto causes &clsql; + to automatically convert SQL fields into a + numeric format where applicable. The default value of + &nil; causes all fields to be returned as strings + regardless of the SQL type. Otherwise a list is expected + which has a element for each field that specifies the + conversion. Valid type identifiers are: + + :int Field is imported as a + signed integer, from 8-bits to 64-bits depending + upon the field type. + + :double Field is imported as a + double-float number. + + t Field is imported as a + string. + + + If the list is shorter than the number of fields, the a + value of t is assumed for the field. + If the list is longer than the number of fields, the + extra elements are ignored. + + + + + field-names + + + A boolean with a default value of &t;. When &t;, this + function returns a second value of a list of field + names. When &nil;, this function only returns one value - + the list of rows. + + + + + refresh + + + This value is only considered when CLOS objects are being + selected. A boolean with a default value of &nil;. When + the value of the caching keyword is + &t;, a second equivalent select call + will return the same view class instance objects. When + refresh is &t;, then slots of the + existing instances are updated as necessary. In such + cases, you may wish to override the hook + instance-refresh. + + + + + caching + + + This value is only considered when CLOS objects are being + selected. A boolean with a default value of + *default-caching*. &clsql; caches + objects in accordance with the &commonsql; interface: a + second equivalent select call will + return the same view class instance objects. + + + + + result + + + A list representing the result set obtained. For each + tuple in the result set, there is an element in this + list, which is itself a list of all the attribute values + in the tuple. + + + + + + + Description + + Executes a query on database, which has + a default value of *default-database*, + specified by the SQL expressions supplied using the remaining + arguments in args. The + select function can be used to generate + queries in both functional and object oriented contexts. + + + In the functional case, the required arguments specify the + columns selected by the query and may be symbolic SQL + expressions or strings representing attribute + identifiers. Type modified identifiers indicate that the + values selected from the specified column are converted to the + specified lisp type. The keyword arguments + all, distinct, + from, group-by, + having, limit, + offset, order-by, + set-operation and + where are used to specify, using the + symbolic SQL syntax, the corresponding components of the SQL + query generated by the call to + select. + + + result-types is a list of symbols which + specifies the lisp type for each field returned by the + query. If result-types is &nil; all + results are returned as strings whereas the default value of + :auto means that the lisp types are + automatically computed for each + field. field-names is &t; by default + which means that the second value returned is a list of + strings representing the columns selected by the query. If + field-names is &nil;, the list of + column names is not returned as a second value. + + + In the object oriented case, the required arguments to + select are symbols denoting View Classes + which specify the database tables to query. In this case, + select returns a list of View Class + instances whose slots are set from the attribute values of the + records in the specified table. Slot-value is + a legal operator which can be employed as part of the symbolic + SQL syntax used in the where keyword + argument to select. + refresh is &nil; by default which means + that the View Class instances returned are retrieved from a + cache if an equivalent call to select has + previously been issued. If refresh is + true, the View Class instances returned are updated as + necessary from the database and the generic function + instance-refreshed is called to perform + any necessary operations on the updated instances. + + + In both object oriented and functional contexts, + flatp has a default value of &nil; + which means that the results are returned as a list of + lists. If flatp is t and only one + result is returned for each record selected in the query, the + results are returned as elements of a list. + + + + Examples + +(select [first-name] :from [employee] :flatp t :distinct t + :field-names nil + :result-types nil + :order-by [first-name]) +=> ("Boris" "Josef" "Konstantin" "Leon" "Leonid" "Mikhail" "Nikita" "Vladimir" + "Yuri") + +(select [first-name] [count [*]] :from [employee] + :result-types nil + :group-by [first-name] + :order-by [first-name] + :field-names nil) +=> (("Boris" "1") ("Josef" "1") ("Konstantin" "1") ("Leon" "1") ("Leonid" "1") + ("Mikhail" "1") ("Nikita" "1") ("Vladimir" "2") ("Yuri" "1")) + +(select [last-name] :from [employee] + :where [like [email] "%org"] + :order-by [last-name] + :field-names nil + :result-types nil + :flatp t) +=> ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin") + +(select [max [emplid]] :from [employee] + :flatp t + :field-names nil + :result-types :auto) +=> (10) + +(select [avg [height]] :from [employee] :flatp t :field-names nil) +=> (1.58999584d0) + +(select [emplid] [last-name] :from [employee] :where [= [emplid] 1]) +=> ((1 "Lenin")), + ("emplid" "last_name") + +(select [emplid :string] :from [employee] + :where [= 1 [emplid]] + :field-names nil + :flatp t) +=> ("1") + +(select [emplid] :from [employee] :order-by [emplid] + :where [not [between [* [emplid] 10] [* 5 10] [* 10 10]]] + :field-names nil + :flatp t) +=> (1 2 3 4) + +(select [emplid] :from [employee] + :where [in [emplid] '(1 2 3 4)] + :flatp t + :order-by [emplid] + :field-names nil) +=> (1 2 3 4) + +(select [emplid] :from [employee] + :order-by [emplid] + :limit 5 + :offset 3 + :field-names nil + :flatp t) +=> (4 5 6 7 8) + +(select [first-name] [last-name] :from [employee] + :field-names nil + :order-by '(([first-name] :asc) ([last-name] :desc))) +=> (("Boris" "Yeltsin") ("Josef" "Stalin") ("Konstantin" "Chernenko") + ("Leon" "Trotsky") ("Leonid" "Brezhnev") ("Mikhail" "Gorbachev") + ("Nikita" "Kruschev") ("Vladimir" "Putin") ("Vladimir" "Lenin") + ("Yuri" "Andropov")) + +(select [last-name] :from [employee] + :set-operation [union [select [first-name] :from [employee] + :order-by [last-name]]] + :flatp t + :result-types nil + :field-names nil) +=> ("Andropov" "Boris" "Brezhnev" "Chernenko" "Gorbachev" "Josef" "Konstantin" + "Kruschev" "Lenin" "Leon" "Leonid" "Mikhail" "Nikita" "Putin" "Stalin" + "Trotsky" "Vladimir" "Yeltsin" "Yuri") + + + + Side Effects + Whatever effects the execution of the SQL query has on + the underlying database, if any. + + + Affected by + + None. + + + + Exceptional Situations + + If the execution of the SQL query leads to any errors, an + error of type sql-database-error is + signalled. + + + + See Also + + query + print-query + do-query + map-query + loop + instance-refreshed + + + + Notes + + The select function is actually + implemented in &clsql; with a single + &rest parameter (which is subsequently + destructured) rather than the keyword parameters presented + here for the purposes of exposition. This means that incorrect + or missing keywords or values may not trigger errors in the + way that they would if select had been + defined using keyword arguments. + + + The field-names and + result-types keyword arguments are a + &clsql; extension. + + + select is common across the functional + and object-oriented data manipulation languages. + + + + + + + + + + DO-QUERY + + + DO-QUERY + Iterate over all the tuples of a query. + Macro + + + Syntax + + do-query ((&rest args) query-expression &key database result-types &body body => result + + + Arguments and Values + + + args + + A list of variable names. + + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set, where each tuple has as many attributes as + function takes arguments. + + + + database + + A + database + object. This will default to + *default-database*. + + + + result-types + + + A field type + specifier. The default is &nil;. See query for + the semantics of this argument. + + + + + body + + A body of Lisp code, like in a + destructuring-bind form. + + + + result + + The result of executing body. + + + + + + Description + + Repeatedly executes body within a + binding of args on the fields of each + row selected by the SQL query + query-expression, which may be a string + or a symbolic SQL expression, in + database which defaults to + *default-database*. + + + The body of code is executed in a block named + nil which may be returned from prematurely + via return or + return-from. In this case the result of + evaluating the do-query form will be the + one supplied to return or + return-from. Otherwise the result will + be nil. + + + The body of code appears also is if wrapped in a + destructuring-bind form, thus allowing + declarations at the start of the body, especially those + pertaining to the bindings of the variables named in + args. + + + result-types is a list of symbols which + specifies the lisp type for each field returned by + query-expression. If + result-types is &nil; all results are + returned as strings whereas the default value of + :auto means that the lisp types are + automatically computed for each field. + + + query-expression may be an object query + (i.e., the selection arguments refer to View Classes), in + which case args are bound to the tuples + of View Class instances returned by the object oriented query. + + + + Examples + +(do-query ((salary name) "select salary,name from simple") + (format t "~30A gets $~2,5$~%" name (read-from-string salary))) +>> Mai, Pierre gets $10000.00 +>> Hacker, Random J. gets $08000.50 +=> NIL + +(do-query ((salary name) "select salary,name from simple") + (return (cons salary name))) +=> ("10000.00" . "Mai, Pierre") + +(let ((result '())) + (do-query ((name) [select [last-name] :from [employee] + :order-by [last-name]]) + (push name result)) + result) +=> ("Yeltsin" "Trotsky" "Stalin" "Putin" "Lenin" "Kruschev" "Gorbachev" + "Chernenko" "Brezhnev" "Andropov") + +(let ((result '())) + (do-query ((e) [select 'employee :order-by [last-name]]) + (push (slot-value e 'last-name) result)) + result) +=> ("Yeltsin" "Trotsky" "Stalin" "Putin" "Lenin" "Kruschev" "Gorbachev" + "Chernenko" "Brezhnev" "Andropov") + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + If the number of variable names in + args and the number of attributes in + the tuples in the result set don't match up, an error is + signalled. + + + See Also + + query + map-query + print-query + loop + select + + + + Notes + The result-types keyword argument + is a &clsql; extension. + + do-query is common across the functional + and object-oriented data manipulation languages. + + + + + + + LOOP + + + LOOP + Extension to Common Lisp + Loop to iterate over all the + tuples of a query via a loop clause. + Loop Clause + + + + Syntax + {as | for} var [type-spec] being {each | the} {record | records | tuple | tuples} {in | of} query [from database] + + + Arguments and Values + + + var + + A d-var-spec, as defined in the + grammar for loop-clauses in the ANSI + Standard for Common Lisp. This allows for the usual + loop-style destructuring. + + + + type-spec + + An optional type-spec either + simple or destructured, as defined in the grammar for + loop-clauses in the ANSI Standard for + Common Lisp. + + + + query + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set, where each tuple has as many attributes as + function takes arguments. + + + + database + + An optional + database + object. This will default to the value + of *default-database*. + + + + + + Description + This clause is an iteration driver for + loop, that binds the given variable + (possibly destructured) to the consecutive tuples (which are + represented as lists of attribute values) in the result set + returned by executing the SQL query + expression on the database + specified. + + query may be an object query (i.e., the + selection arguments refer to View Classes), in which case the + supplied variable is bound to the tuples of View Class + instances returned by the object oriented query. + + + + Examples + +(defvar *my-db* (connect '("dent" "newesim" "dent" "dent")) +"My database" +=> *MY-DB* +(loop with time-graph = (make-hash-table :test #'equal) + with event-graph = (make-hash-table :test #'equal) + for (time event) being the tuples of "select time,event from log" + from *my-db* + do + (incf (gethash time time-graph 0)) + (incf (gethash event event-graph 0)) + finally + (flet ((show-graph (k v) (format t "~40A => ~5D~%" k v))) + (format t "~&Time-Graph:~%===========~%") + (maphash #'show-graph time-graph) + (format t "~&~%Event-Graph:~%============~%") + (maphash #'show-graph event-graph)) + (return (values time-graph event-graph))) +>> Time-Graph: +>> =========== +>> D => 53000 +>> X => 3 +>> test-me => 3000 +>> +>> Event-Graph: +>> ============ +>> CLOS Benchmark entry. => 9000 +>> Demo Text... => 3 +>> doit-text => 3000 +>> C Benchmark entry. => 12000 +>> CLOS Benchmark entry => 32000 +=> #<EQUAL hash table, 3 entries {48350A1D}> +=> #<EQUAL hash table, 5 entries {48350FCD}> + +(loop for (forename surname) + being each tuple in + [select [first-name] [last-name] :from [employee] + :order-by [last-name]] + collect (concatenate 'string forename " " surname)) +=> ("Yuri Andropov" "Leonid Brezhnev" "Konstantin Chernenko" "Mikhail Gorbachev" + "Nikita Kruschev" "Vladimir Lenin" "Vladimir Putin" "Josef Stalin" + "Leon Trotsky" "Boris Yeltsin") + +(loop for (e) being the records in + [select 'employee :where [< [emplid] 4] :order-by [emplid]] + collect (slot-value e 'last-name)) +=> ("Lenin" "Stalin" "Trotsky") + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + Otherwise, any of the exceptional situations of + loop applies. + + + See Also + + + query + map-query + do-query + print-query + select + + + + + Notes + The database loop keyword is a + &clsql; extension. + + The extended loop syntax is common across + the functional and object-oriented data manipulation + languages. + + + + + + + MAP-QUERY + + + MAP-QUERY + Map a function over all the tuples from a + query + Function + + + Syntax + map-query output-type-spec function query-expression &key database result-types => result + + + Arguments and Values + + + output-type-spec + + A sequence type specifier or nil. + + + + function + + A function designator. + function takes a single argument which + is the atom value for a query single with a single column + or is a list of values for a multi-column query. + + + + query-expression + + An sql + expression that represents an SQL + query which is expected to return a (possibly empty) + result set. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result-types + + + A field type + specifier. The default is &nil;. See query for + the semantics of this argument. + + + + + result + + If output-type-spec is a + type specifier other than nil, then a + sequence of the type it denotes. Otherwise + nil is returned. + + + + + + Description + + Applies function to the successive + tuples in the result set returned by executing the SQL + query-expression. If the + output-type-spec is + nil, then the result of each application of + function is discarded, and + map-query returns nil. + Otherwise the result of each successive application of + function is collected in a sequence of + type output-type-spec, where the jths + element is the result of applying + function to the attributes of the jths + tuple in the result set. The collected sequence is the result + of the call to map-query. + + + If the output-type-spec is a subtype of + list, the result will be a list. + + + If the result-type is a subtype of + vector, then if the implementation can determine + the element type specified for the + result-type, the element type of the + resulting array is the result of + upgrading that element type; or, if the + implementation can determine that the element type is + unspecified (or *), the element type of the + resulting array is t; otherwise, an error is + signaled. + + + If result-types is &nil; all results + are returned as strings whereas the default value of + :auto means that the lisp types are + automatically computed for each field. + + query-expression may be an object query + (i.e., the selection arguments refer to View Classes), in + which case the supplied function is applied to the tuples of + View Class instances returned by the object oriented query. + + + + Examples + +(map-query 'list #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (declare (ignorable name)) + (read-from-string salary))) + "select salary,name from simple where salary > 8000") +=> (10000.0 8000.5) + +(map-query '(vector double-float) + #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (declare (ignorable name)) + (let ((*read-default-float-format* 'double-float)) + (coerce (read-from-string salary) 'double-float)) + "select salary,name from simple where salary > 8000"))) +=> #(10000.0d0 8000.5d0) +(type-of *) +=> (SIMPLE-ARRAY DOUBLE-FLOAT (2)) + +(let (list) + (values (map-query nil #'(lambda (tuple) + (multiple-value-bind (salary name) tuple + (push (cons name (read-from-string salary)) list)) + "select salary,name from simple where salary > 8000")) + list)) +=> NIL +=> (("Hacker, Random J." . 8000.5) ("Mai, Pierre" . 10000.0)) + +(map-query 'vector #'identity + [select [last-name] :from [employee] :flatp t + :order-by [last-name]]) +=> #("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin") + +(map-query 'list #'identity + [select [first-name] [last-name] :from [employee] + :order-by [last-name]]) +=> (("Yuri" "Andropov") ("Leonid" "Brezhnev") ("Konstantin" "Chernenko") + ("Mikhail" "Gorbachev") ("Nikita" "Kruschev") ("Vladimir" "Lenin") + ("Vladimir" "Putin") ("Josef" "Stalin") ("Leon" "Trotsky") + ("Boris" "Yeltsin")) + +(map-query 'list #'last-name [select 'employee :order-by [emplid]]) +=> ("Lenin" "Stalin" "Trotsky" "Kruschev" "Brezhnev" "Andropov" "Chernenko" + "Gorbachev" "Yeltsin" "Putin") + + + + Side Effects + Whatever effects the execution of the SQL query has + on the underlying database, if any. + + + Affected by + None. + + + Exceptional Situations + If the execution of the SQL query leads to any + errors, an error of type + sql-database-error is signalled. + An error of type type-error must + be signaled if the output-type-spec is + not a recognizable subtype of list, not a + recognizable subtype of vector, and not + nil. + An error of type type-error + should be signaled if + output-type-spec specifies the number + of elements and the size of the result set is different from + that number. + + + See Also + + query + do-query + print-query + loop + select + + + + Notes + The result-types keyword argument + is a &clsql; extension. + + map-query is common across the + functional and object-oriented data manipulation languages. + + + + + + diff --git a/doc/ref-lob.xml b/doc/ref-lob.xml new file mode 100644 index 0000000..4ca04ff --- /dev/null +++ b/doc/ref-lob.xml @@ -0,0 +1,273 @@ + + +%myents; +]> + + + Large Object Support + + + + + + + + + CREATE-LARGE-OBJECT + + Function + + + Syntax + + (CREATE-LARGE-OBJECT &KEY (DATABASE *DEFAULT-DATABASE*)) [function] => + + + Arguments and Values + + + + + + Description + Creates a new large object in the database and returns + the object identifier + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + DELETE-LARGE-OBJECT + + Function + + + Syntax + + (DELETE-LARGE-OBJECT OBJECT-ID &KEY (DATABASE *DEFAULT-DATABASE*)) [function] => + + + Arguments and Values + + + + + + Description + Deletes the large object in the database + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + READ-LARGE-OBJECT + + Function + + + Syntax + + (READ-LARGE-OBJECT OBJECT-ID &KEY (DATABASE *DEFAULT-DATABASE*)) [function] => + + + Arguments and Values + + + + + + Description + Reads the large object content + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + WRITE-LARGE-OBJECT + + Function + + + Syntax + + (WRITE-LARGE-OBJECT OBJECT-ID DATA &KEY (DATABASE *DEFAULT-DATABASE*)) [function] => + + + Arguments and Values + + + + + + Description + Writes data to the large object + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + diff --git a/doc/ref-ooddl.xml b/doc/ref-ooddl.xml new file mode 100644 index 0000000..4a2cffa --- /dev/null +++ b/doc/ref-ooddl.xml @@ -0,0 +1,1149 @@ + + +%myents; +]> + + + + Object Oriented Data Definition Language (OODDL) + + + The Object Oriented Data Definition Language (OODDL) provides + access to relational SQL tables using Common Lisp Object System + (CLOS) objects. SQL tables are mapped to CLOS objects with the + SQL columns being mapped to slots of the CLOS object. + + + The mapping between SQL tables and CLOS objects is defined with + the macro def-view-class. SQL + tables are created with create-view-from-class + and SQL tables can be deleted with drop-view-from-class. + + + The above functions refer to the Lisp + view of the SQL table. This Lisp view + should not be confused with SQL VIEW + statement. + + + + + + STANDARD-DB-OBJECT + Superclass for all &clsql; View Classes. + Class + + + Class Precedence List + + + standard-db-object + standard-object + t + + + + + Description This class is the superclass + of all &clsql; View Classes. + + + Class details + (defclass STANDARD-DB-OBJECT ()(...)) + + + Slots + + + slot VIEW-DATABASE is of type (OR NULL DATABASE) + which stores the associated database for the + instance. + + + + + + + + *DEFAULT-STRING-LENGTH* + Default length of SQL strings. + Variable + + + Value Type + + Fixnum + + + + Initial Value + 255 + + + Description + + If a slot of a class defined by + def-view-class is of the type + string or + varchar and does not have a length + specified, then the value of this variable is used as SQL + length. + + + + Examples + +(let ((*default-string-length* 80)) + (def-view-class s80 () + ((a :type string) + (b :type (string 80)) + (c :type varchar)))) +=> #<Standard-Db-Class S80 {480A431D}> + +(create-view-from-class 's80) +=> +(table-exists-p [s80]) +=> T + + + The above code causes a SQL table to be created with the SQL command + + CREATE TABLE (A VARCHAR(80), B CHAR(80), C VARCHAR(80)) + + + Affected By + + Some SQL backends do not support + varchar lengths greater than 255. + + + + See Also + None. + + + Notes + This is a CLSQL extension to the CommonSQL API. + + + + + + CREATE-VIEW-FROM-CLASS + Create a SQL table from a View Class. + Function + + + Syntax + + create-view-from-class view-class-name &key database transactions => + + + Arguments and Values + + + view-class-name + + + The name of a View + Class that has been defined with def-view-class. + + + + + database + + + The database in + which to create the SQL table. This will default to the + value of *default-database*. + + + + + transactions + + + When &nil; specifies that a table type which does not + support transactions should be used. + + + + + + + Description + + Creates a table as defined by the View Class + view-class-name in + database. + + + + Examples + +(def-view-class foo () ((a :type (string 80)))) +=> #<Standard-Db-Class FOO {4807F7CD}> +(create-view-from-class 'foo) +=> +(list-tables) +=> ("FOO") + + + + Side Effects + + Causes a table to be created in the SQL database. + + + + Affected by + + Most SQL database systems will signal an error if a table + creation is attempted when a table with the same name already + exists. The SQL user, as specified in the database connection, + must have sufficient permission for table creation. + + + + Exceptional Situations + + A condition will be signaled if the table can not be created + in the SQL database. + + + + See Also + + + def-view-class + drop-view-from-class + + + + + Notes + + Currently, only &mysql; supports transactionless + tables. &clsql; provides the ability to create such tables for + applications which would benefit from faster table access and + do not require transaction support. + + + The case of the table name is determined by the type of the + database. &mysql;, for example, creates databases in upper-case + while &postgresql; uses lowercase. + + + + + + + DEF-VIEW-CLASS + Defines CLOS classes with mapping to SQL database. + Macro + + + Syntax + + def-view-class name superclasses slots &rest class-options => class + + + Arguments and Values + + + name + + + The class name. + + + + + superclasses + + + The superclasses for the defined class. + + + + + slots + + + The class slot definitions. + + + + + class options + + + The class options. + + + + + class + + + The defined class. + + + + + + + Slot Options + + + + :db-kind - specifies the kind of + database mapping which is performed for this slot and + defaults to :base which indicates + that the slot maps to an ordinary column of the database + table. A :db-kind value of + :key indicates that this slot is a + special kind of :base slot which + maps onto a column which is one of the unique keys for the + database table, the value :join + indicates this slot represents a join onto another + View Class + which contains View Class objects, and the value + :virtual indicates a standard CLOS + slot which does not map onto columns of the database + table. + + + + + :db-info - if a slot is specified + with :db-kind + :join, the slot option + :db-info contains a property list + which specifies the nature of the join. The valid members + of the list are: + + + + + :join-class + class-name - the name of the + class to join on. + + + + + :home-key + slot-name - the name of the slot + of this class for joining + + + + + :foreign-key + slot-name - the name of the slot + of the :join-class for joining + + + + + :target-slot + target-slot - this is an optional + parameter. If specified, then the join slot of the + defining class will contain instances of this target + slot rather than of the join class. This can be useful + when the :join-class is an + intermediate class in a + many-to-many relationship and the + application is actually interested in the + :target-slot. + + + + + :retrieval + time - The default value is + :deferred, which defers filling + this slot until the value is accessed. The other valid + value is :immediate which + performs the SQL query when the instance of the class + is created. In this case, the + :set is automatically set to + &nil; + + + + + :set set - + This controls what is stored in the join slot. The + default value is &t;. When set is + &t; and target-slot is undefined, + the join slot will contain a list of instances of the + join class. Whereas, if + target-slot is defined, then the + join slot will contain a list of pairs of + (target-value join-instance). + When set is &nil;, the join slot + will contain a single instances. + + + + + + + :type - for slots of + :db-kind :base or + :key, the :type slot + option has a special interpretation such that Lisp + types, such as string, integer and float are + automatically converted into appropriate SQL types for + the column onto which the slot maps. This behaviour may + be overridden using the :db-type slot + option. The valid values are: + + + string - a variable length + character field up to *default-string-length* + characters. + + + (string n) - a fixed length + character field n characters + long. + + + varchar - a variable length + character field up to *default-string-length* + characters. + + + (varchar n) - a variable length + character field up to n + characters in length. + + + char - a single character field + + integer - signed integer + at least 32-bits wide + (integer n) + float + (float n) + long-float + number + (number n) + (number n p) + + tinyint - An integer column 8-bits + wide. [not supported by all database backends] + + + smallint - An integer column 16-bits + wide. [not supported by all database backends] + + + bigint - An integer column + 64-bits wide. [not supported by all database backends] + + + universal-time - an integer + field sufficiently wide to store a + universal-time. On most databases, a slot of this + type assigned a SQL type of + BIGINT + + + wall-time - a slot which stores + a date and time in a SQL timestamp column. &clsql; + provides a number of time manipulation functions to + support objects of type wall-time. + + + date - a slot which stores the + date (without any time of day resolution) in a + column. &clsql; provides a number of time + manipulation functions that operate on date values. + + + duration - stores a + duration structure. &clsql; provides + routines for wall-time and + duration processing. + + boolean - stores a &t; or + &nil; value. + + generalized-boolean - similar + to a boolean in that either a + &t; or &nil; value is stored in the SQL + database. However, any Lisp object can be stored in + the Lisp object. A Lisp value of &nil; is stored as + FALSE in the database, any + other Lisp value is stored as + TRUE. + + + keyword - stores a keyword + + symbol - stores a symbol + + list - stores a list by writing + it to a string. The items in the list must be able to + be readable written. + + vector - stores a vector + similarly to list + array - stores a array + similarly to list + + + + + + + :column - specifies the name of + the SQL column which the slot maps onto, if + :db-kind is not + :virtual, and defaults to the + slot name. If the slot name is used for the SQL column + name, any hypens in the slot name are converted + to underscore characters. + + + + + :void-value - specifies the value + to store in the Lisp instance if the SQL value is NULL and + defaults to NIL. + + + + + :db-constraints - is a keyword + symbol representing an SQL column constraint expression or + a list of such symbols. The following column constraints + are supported: :not-null, + :primary-key, :unique, + :unsigned (&mysql; specific), + :zerofill (&mysql; specific) and + :auto-increment (&mysql; specific). + + + + + :db-type - a string to specify the SQL + column type. If specified, this string overrides the SQL + column type as computed from the :type + slot value. + + + + + :db-reader - If a string, then when + reading values from the database, the string will be used + for a format string, with the only value being the value + from the database. The resulting string will be used as + the slot value. If a function then it will take one + argument, the value from the database, and return the + value that should be put into the slot. If a symbol, then + the symbol-function of the symbol will be used. + + + + + :db-writer - If a string, then when + reading values from the slot for the database, the string + will be used for a format string, with the only value + being the value of the slot. The resulting string will be + used as the column value in the database. If a function + then it will take one argument, the value of the slot, and + return the value that should be put into the database. If + a symbol, then the symbol-function of the symbol will be + used. + + + + + + Class Options + + + + + :base-table - specifies the name + of the SQL database table. The default value is the + class name. Like slot names, hypens in the class name + are converted to underscore characters. + + + + + :normalizedp - specifies whether + this class uses normalized inheritance from parent classes. + Defaults to nil, i.e. non-normalized schemas. When true, + SQL database tables that map to this class and parent + classes are joined on their primary keys to get the full + set of database columns for this class. This means that + the primary key of the base class will be copied to all + subclasses as we insert so that all parent classes of an + instance will have the same value in their primary key slots + (see tests/ds-nodes.lisp and oodml.lisp) + + + + + + + Description + + Creates a View + Class called name whose + slots slots can map onto the attributes + of a table in a database. If + superclasses is &nil; then the + superclass of class will be + standard-db-object, otherwise + superclasses is a list of superclasses + for class which must include + standard-db-object or a descendent of + this class. + + + + Normalized inheritance schemas + + Specifying that :normalizedp is T + tells &clsql; to normalize the database schema for inheritance. + What this means is shown in the examples below. + + + + With :normalizedp equal to NIL + (the default) the class inheritance would result in the following: + + +(def-view-class node () + ((title :accessor title :initarg :title :type (varchar 240)))) + +SQL table NODE: ++-------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-------+--------------+------+-----+---------+-------+ +| TITLE | varchar(240) | YES | | NULL | | ++-------+--------------+------+-----+---------+-------+ + +(def-view-class user (node) + ((user-id :accessor user-id :initarg :user-id + :type integer :db-kind :key :db-constraints (:not-null)) + (nick :accessor nick :initarg :nick :type (varchar 64)))) + +SQL table USER: ++---------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+--------------+------+-----+---------+-------+ +| USER_ID | int(11) | NO | PRI | | | +| NICK | varchar(64) | YES | | NULL | | +| TITLE | varchar(240) | YES | | NULL | | ++---------+--------------+------+-----+---------+-------+ + + + + Using :normalizedp T, both + view-classes need a primary key to join them on: + + +(def-view-class node () + ((node-id :accessor node-id :initarg :node-id + :type integer :db-kind :key + :db-constraints (:not-null)) + (title :accessor title :initarg :title :type (varchar 240)))) + +SQL table NODE: ++---------+--------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+--------------+------+-----+---------+-------+ +| NODE_ID | int(11) | NO | PRI | | | +| TITLE | varchar(240) | YES | | NULL | | ++---------+--------------+------+-----+---------+-------+ + +(def-view-class user (node) + ((user-id :accessor user-id :initarg :user-id + :type integer :db-kind :key :db-constraints (:not-null)) + (nick :accessor nick :initarg :nick :type (varchar 64))) + (:normalizedp t)) + +SQL table USER: ++---------+-------------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++---------+-------------+------+-----+---------+-------+ +| USER_ID | int(11) | NO | PRI | | | +| NICK | varchar(64) | YES | | NULL | | ++---------+-------------+------+-----+---------+-------+ + + + + In this second case, all slots of the view-class 'node + are also available in view-class 'user, and can be used + as one would expect. For example, with the above normalized + view-classes 'node and 'user, and SQL tracing turned on: + + +CLSQL> (setq test-user (make-instance 'user :node-id 1 :nick "test-user" + :title "This is a test user")) +]]> + +CLSQL> (update-records-from-instance test-user :database db) + INSERT INTO NODE (NODE_ID,TITLE) VALUES (1,'This is a test user') +;; .. <= T +;; .. => INSERT INTO USER (USER_ID,NICK) VALUES (1,'test-user') +;; .. <= T +1 +]]> + +CLSQL> (node-id test-user) +1 + +CLSQL> (title test-user) +"This is a test user" + +CLSQL> (nick test-user) +"test-user" + + + Notes from a refactor of this code. + + There are many assumptions that need to be met for normalized classes to work + + * The each of the classes should have its own single key column (of a different name) + that will contain an identical value. EG: node has a node-id, setting which + is a node has a node-id and a setting-id which must be equal. You cannot use + node-id as the primary key on both tables (as I would have expected). The exception + to this seems to be if your class has no slots at all, then you dont need to have a + single key column, because your class is fully represented in the db by its parent(s) + + * more than one parent class per normalized class should be considered experimental + and untested (vaya con Dios) + + * There are a few code paths that just dont pay any attention to normalized classes + eg: delete-records-for-instance + + + + + + Examples + + The following examples are from the &clsql; test suite. + + +(def-view-class person (thing) + ((height :db-kind :base :accessor height :type float + :initarg :height) + (married :db-kind :base :accessor married :type boolean + :initarg :married) + (birthday :type clsql:wall-time :initarg :birthday) + (bd-utime :type clsql:universal-time :initarg :bd-utime) + (hobby :db-kind :virtual :initarg :hobby :initform nil))) + +(def-view-class employee (person) + ((emplid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :emplid) + (groupid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :groupid) + (first-name + :accessor first-name + :type (varchar 30) + :initarg :first-name) + (last-name + :accessor last-name + :type (varchar 30) + :initarg :last-name) + (email + :accessor employee-email + :type (varchar 100) + :initarg :email) + (ecompanyid + :type integer + :initarg :companyid) + (company + :accessor employee-company + :db-kind :join + :db-info (:join-class company + :home-key ecompanyid + :foreign-key companyid + :set nil)) + (managerid + :type integer + :initarg :managerid) + (manager + :accessor employee-manager + :db-kind :join + :db-info (:join-class employee + :home-key managerid + :foreign-key emplid + :set nil)) + (addresses + :accessor employee-addresses + :db-kind :join + :db-info (:join-class employee-address + :home-key emplid + :foreign-key aemplid + :target-slot address + :set t))) + (:base-table employee)) + +(def-view-class company () + ((companyid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :companyid) + (groupid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :groupid) + (name + :type (varchar 100) + :initarg :name) + (presidentid + :type integer + :initarg :presidentid) + (president + :reader president + :db-kind :join + :db-info (:join-class employee + :home-key presidentid + :foreign-key emplid + :set nil)) + (employees + :reader company-employees + :db-kind :join + :db-info (:join-class employee + :home-key (companyid groupid) + :foreign-key (ecompanyid groupid) + :set t)))) + +(def-view-class address () + ((addressid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :addressid) + (street-number + :type integer + :initarg :street-number) + (street-name + :type (varchar 30) + :void-value "" + :initarg :street-name) + (city + :column "city_field" + :void-value "no city" + :type (varchar 30) + :initarg :city) + (postal-code + :column zip + :type integer + :void-value 0 + :initarg :postal-code)) + (:base-table addr)) + +;; many employees can reside at many addressess +(def-view-class employee-address () + ((aemplid :type integer :initarg :emplid) + (aaddressid :type integer :initarg :addressid) + (verified :type boolean :initarg :verified) + (address :db-kind :join + :db-info (:join-class address + :home-key aaddressid + :foreign-key addressid + :retrieval :immediate))) + (:base-table "ea_join")) + +(def-view-class deferred-employee-address () + ((aemplid :type integer :initarg :emplid) + (aaddressid :type integer :initarg :addressid) + (verified :type boolean :initarg :verified) + (address :db-kind :join + :db-info (:join-class address + :home-key aaddressid + :foreign-key addressid + :retrieval :deferred + :set nil))) + (:base-table "ea_join")) + + + + Side Effects + Creates a new CLOS class. + + + Affected by + + Nothing. + + + + Exceptional Situations + + None. + + + + See Also + + + create-view-from-class + standard-db-object + drop-view-from-class + + + + + Notes + + The actual SQL type for a column depends up the database type + in which the SQL table is stored. As an example, the view + class type (varchar 100) specifies a + SQL column type VARCHAR(100) in &mysql; + and a column type VARCHAR2(100) in + &oracle; + + + The actual lisp type for a slot may be different than the + value specified by the :type attribute. + For example, a slot declared with ":type (string + 30)" actually sets the slots Lisp type as + (or null string). This is to allow a + &nil; value or a string shorter than 30 characters to be + stored in the slot. + + + + + + + DROP-VIEW-FROM-CLASS + Delete table from SQL database. + Function + + + Syntax + + drop-view-from-class view-class-name &key database => + + + Arguments and Values + + + view-class-name + + + The name of the View + Class. + + + + + database + + + database + object. This will default to the value of + *default-database*. + + + + + + + Description + Removes a table defined by the View Class + view-class-name from + database which defaults to + *default-database*. + + + + Examples + +(list-tables) +=> ("FOO" "BAR") +(drop-view-from-class 'foo) +=> +(list-tables) +=> ("BAR") + + + + Side Effects + + Deletes a table from the SQL database. + + + + Affected by + + Whether the specified table exists in the SQL database. + + + + Exceptional Situations + + A condition may be signalled if the table does not exist in + the SQL database or if the SQL connection does not have + sufficient permissions to delete tables. + + + + See Also + + + create-view-from-class + def-view-class + + + + + Notes + + None. + + + + + + + LIST-CLASSES + List classes for tables in SQL database. + Function + + + Syntax + + list-classes &key test root-class database => classes + + + Arguments and Values + + + test + + + a function used to filter the search. By default, identity is used which + will return all classes. + + + + + root-class + + + specifies the root class to the search. By default, + standard-db-object is used which + is the root for all view classes. + + + + + database + + + The database to + search for view classes. This will default to the value + of *default-database*. + + + + + classes + + + List of view classes. + + + + + + + Description + Returns a list of all the View Classes which have been + defined in the Lisp session and are connected to + database and which descended from the + class root-class and which satisfy the + function test. + + + + Examples + +(list-classes) +=> (#<clsql-sys::standard-db-class big> #<clsql-sys::standard-db-class employee-address> + #<clsql-sys::standard-db-class address> #<clsql-sys::standard-db-class company> + #<clsql-sys::standard-db-class employee>) + +(list-classes :test #'(lambda (c) (> (length (symbol-name (class-name c))) 3))) +=> (#<clsql-sys::standard-db-class employee-address> #<clsql-sys::standard-db-class address> + #<clsql-sys::standard-db-class company> #<clsql-sys::standard-db-class employee>) + + + + Side Effects + + None. + + + + Affected by + + + Which view classes have been defined in the Lisp + session. + + + + + Exceptional Situations + + None. + + + + See Also + + + def-view-class + + + + + Notes + + None. + + + + + + diff --git a/doc/ref-oodml.xml b/doc/ref-oodml.xml new file mode 100644 index 0000000..8a34235 --- /dev/null +++ b/doc/ref-oodml.xml @@ -0,0 +1,1078 @@ + + +%myents; +]> + + + + Object Oriented Data Manipulation Language (OODML) + + + Object Oriented Data Manipulation Language (OODML) provides a + Common Lisp Object System (CLOS) interface to SQL + databases. View classes are defined with the OODDL interface and objects are read + and written with the OODML. + + + The main function for reading data with the OODML is the select + function. The select is also used in the + FDML. However, when select is given a view + class name, it returns a list of instances of view classes. + + + View class instances can be updated to reflect any changes in + the database with the functions update-slot-from-record + and update-instance-from-records. + + To update the database to reflect changes made to instances of view classes, use the functions update-records-from-instance, + update-record-from-slot, and + update-record-from-slots. + + + The function delete-instance-records + deletes the records corresponding to an instance of a view + class. + + + + + + *DB-AUTO-SYNC* + Enables SQL storage during Lisp object creation. + Variable + + + Value Type + + Boolean + + + + Initial Value + &nil; + + + Description + + When this variable is &t; an instance is stored in the SQL + database when the instance is created by + make-instance. Furthermore, the + appropriate database records are updated whenever the slots of + a View Class + instance are modified. + + + When this variable is &nil;, which is the default value, + &clsql; behaves like &commonsql;: instances of view classes + are stored or updated in the SQL database only when update-record-from-instance, + update-record-from-slot + or update-record-from-slots + are called. + + + + Examples + + (let ((instance (make-instance 'foo))) + (update-records-from-instance instance)) + + ;; is equivalent to + + (let ((*db-auto-sync* t)) + (make-instance 'foo)) + + ;; and + + (progn + (setf (slot-value instance 'bar) "baz") + (update-record-from-slot instance 'bar)) + + ;; is equivalent to + + (let ((*db-auto-sync* t)) + (setf (slot-value instance 'bar) "baz")) + + + + Affected By + None. + + + See Also + + update-records-from-instance + update-record-from-slot + update-record-from-slots + + + + Notes + This is a CLSQL extension to the CommonSQL API. + + + + + + *DEFAULT-CACHING* + Controls the default caching behavior. + Variable + + + Value Type + + Boolean + + + + Initial Value + &t; + + + Description + + This variable stores the default value of the + CACHING keyword for the select. + + + + Examples + + (let ((*default-caching* nil))) + (select 'foo)) + + ;; is equivalent to + + (select 'foo :caching nil) + + + + Affected By + None. + + + See Also + + select + + + + Notes This is a CLSQL extension to the + &commonsql; API. &commonsql; has caching on at all times. + + + + + + *DEFAULT-UPDATE-OBJECTS-MAX-LEN* + The default maximum number of objects each query to perform a join + Variable + + + Value Type + + (or null integer) + + + + Initial Value + &nil; + + + Description + + This special variable provides the default value for the + max-len argument of the function update-object-joins. + + + + Examples + + (setq *default-update-objects-max-len* 100) + + + + Affected By + None. + + + See Also + + update-object-joins + + + + Notes + None. + + + + + + INSTANCE-REFRESHED + User hook to call on object refresh. + Generic function + + + Syntax + + instance-refreshed object => + + + Arguments and Values + + + object + + + The View Class object which is being refreshed. + + + + + + + Description + Provides a hook which is called within an object oriented + call to select with a non-nil value of + refresh when the View Class instance + object has been updated from the + database. A method specialised on + standard-db-object is provided which has no + effects. Methods specialised on particular View Classes can be + used to specify any operations that need to be made on View + Classes instances which have been updated in calls to + select. + + + + Examples + +(slot-value employee1 'email) +=> "lenin@soviet.org" +(defmethod instance-refreshed ((e employee)) + (format t "~&Details for ~A ~A have been updated from the database." + (slot-value e 'first-name) + (slot-value e 'last-name))) +=> #<Standard-Method INSTANCE-REFRESHED (EMPLOYEE) {48174D9D}> +(select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t) +=> (#<EMPLOYEE {48149995}>) +(slot-value (car *) 'email) +=> "lenin@soviet.org" +(update-records [employee] :av-pairs '(([email] "v.lenin@soviet.org")) + :where [= [emplid] 1]) +=> +(select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t) +=> (#<EMPLOYEE {48149995}>) +(slot-value (car *) 'email) +=> "lenin@soviet.org" +(select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t :refresh t) +Details for Vladimir Lenin have been updated from the database. +=> (#<EMPLOYEE {48149995}>) +(slot-value (car *) 'email) +=> "v.lenin@soviet.org" + + + + Side Effects + + The user hook function may cause side effects. + + + + Exceptional Situations + + None. + + + + See Also + + + select + + + + + Notes + + None. + + + + + + + DELETE-INSTANCE-RECORDS + Delete SQL records represented by a View Class + object. + Function + + + Syntax + + delete-instance-records object => + + + Arguments and Values + + + object + + + An instance of a View + Class. + + + + + + + Description + Deletes the records represented by + object in the appropriate table of the + database associated with object. If + object is not yet associated with a + database, an error is signalled. + + + + Examples + +(def-view-class tab () + ((a :initarg :a :type integer :db-kind :key) + (b :initarg :b :type string))) +=> #<Standard-Db-Class TAB {49B01845}> +(create-view-from-class 'tab) +=> +(defvar obj (let ((*db-auto-sync* t)) + (make-instance 'tab :a 5 :b "the string"))) +=> OBJ +(start-sql-recording :type :both) +=> +(delete-instance-records obj) +;; 2004-07-17 11:07:19 foo/bar/baz => DELETE FROM tab WHERE tab.a = 5 +;; 2004-07-17 11:07:19 foo/bar/baz <= T +=> + + + + Side Effects + + Deletes data from the SQL database. + + + + Affected by + + Permissions granted by the SQL database to the user in the + database connection. + + + + Exceptional Situations + + An exception may be signaled if the database connection user + does not have sufficient privileges to modify the database. An + error of type sql-database-error is signalled if + object is not associated with an + active database. + + + + See Also + + update-records + delete-records + update-records-from-instance + + + + Notes + + Instances are referenced in the database by values stored in + the key slots. If + delete-records-from-instance is called + with an instance of a class that does not contain any keys, + then all records in that table will be deleted. + + + + + + + UPDATE-RECORDS-FROM-INSTANCE + Update database from view class object. + Function + + + Syntax + + update-records-from-instance object &key database => + + + Arguments and Values + + + object + + + An instance of a View + Class. + + + + + database + + + database + object. This will default to the value of + *default-database*. + + + + + + + Description + Using an instance of a View Class, + object, update the table that stores its + instance data. database specifies the + database in which the update is made only if + object is not associated with a + database. In this case, a record is created in the appropriate + table of database using values from the + slot values of object, and + object becomes associated with + database. + + + + Examples + +(select [email] :from [employee] :where [= [emplid] 1] :field-names nil :flatp t) +=> ("lenin@soviet.org") +(defvar *e1* (car (select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t))) +=> *E1* +(slot-value *e1* 'email) +=> "lenin@soviet.org" +(setf (slot-value *e1* 'email) "v.lenin@soviet.org") +=> "v.lenin@soviet.org" +(update-records-from-instance *e1*) +=> +(select [email] :from [employee] :where [= [emplid] 1] :field-names nil :flatp t) +=> ("v.lenin@soviet.org") + + + + Side Effects + + Modifies the database. + + + + Affected by + + Nothing. + + + + Exceptional Situations + + Database errors. + + + + See Also + + update-record-from-slot + update-record-from-slots + update-records + + + + Notes + + None. + + + + + + + UPDATE-RECORD-FROM-SLOT + Updates database from slot value. + Function + + + Syntax + + update-record-from-slot object slot &key database => + + + Arguments and Values + + + object + + + An instance of a View Class. + + + + + slot + + + The name of a slot in object. + + + + + database + + + A database + object. This will default to the value of + *default-database*. + + + + + + + Description + Updates the value stored in the column represented by the + slot, specified by the CLOS slot name + slot, of View Class instance + object. database + specifies the database in which the update is made only if + object is not associated with a + database. In this case, a record is created in + database and the attribute represented by + slot is initialised from the value of the + supplied slots with other attributes having default + values. Furthermore, object becomes + associated with database. + + + + Examples + +(select [email] :from [employee] :where [= [emplid] 1] :field-names nil :flatp t) +=> ("lenin@soviet.org") +(defvar *e1* (car (select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t))) +=> *E1* +(slot-value *e1* 'email) +=> "lenin@soviet.org" +(setf (slot-value *e1* 'email) "v.lenin@soviet.org") +=> "v.lenin@soviet.org" +(update-record-from-slot *e1* 'email) +=> +(select [email] :from [employee] :where [= [emplid] 1] :field-names nil :flatp t) +=> ("v.lenin@soviet.org") + + + + Side Effects + + Modifies database. + + + + Affected By + + Nothing. + + + + Exceptional Situations + + Database errors. + + + + See Also + + update-record-from-slots + update-records-from-instance + + + + Notes + + None. + + + + + + + UPDATE-RECORD-FROM-SLOTS + Update database from slots of view class object. + function + + + syntax + + update-record-from-slots object slots &key database => + + + Arguments and Values + + + object + + + An instance of a View Class. + + + + + slots + + + A list of slot names in object. + + + + + database + + + A database + object. This will default to the value of + *default-database*. + + + + + + + Description + Updates the values stored in the columns represented by + the slots, specified by the clos slot names + slots, of View Class instance + object. database + specifies the database in which the update is made only if + object is not associated with a + database. In this case, a record is created in the appropriate + table of database and the attributes + represented by slots are initialised from + the values of the supplied slots with other attributes having + default values. Furthermore, object + becomes associated with database. + + + + Examples + +(select [last-name] [email] :from [employee] :where [= [emplid] 1] :field-names nil) +=> (("Lenin" "lenin@soviet.org")) +(defvar *e1* (car (select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t))) +=> *E1* +(slot-value *e1* 'last-name) +=> "Lenin" +(slot-value *e1* 'email) +=> "lenin@soviet.org" +(setf (slot-value *e1* 'last-name) "Ivanovich") +=> "Ivanovich" +(setf (slot-value *e1* 'email) "v.ivanovich@soviet.org") +=> "v.ivanovich@soviet.org" +(update-record-from-slots *e1* '(email last-name)) +=> +(select [last-name] [email] :from [employee] :where [= [emplid] 1] :field-names nil) +=> (("Ivanovich" "v.ivanovich@soviet.org")) + + + + Side Effects + + Modifies the SQL database. + + + + Affected by + + Nothing. + + + + Exceptional Situations + + Database errors. + + + + See Also + + + update-record-from-slot + update-records-from-instance + + + + + Notes + + None. + + + + + + + UPDATE-INSTANCE-FROM-RECORDS + Update slot values from database. + Function + + + Syntax + + update-instance-from-records object &key database => object + + + Arguments and Values + + + object + + + An instance of a View Class. + + + + + database + + + A database + object. This will default to the value of + *default-database*. + + + + + + + Description + Updates the slot values of the View Class instance + object using the attribute values of the + appropriate table of database which + defaults to the database associated with + object or, if + object is not associated with a database, + *default-database*. Join slots are updated + but instances of the class on which the join is made are not + updated. + + + + Examples + +(defvar *e1* (car (select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t))) +=> *E1* +(slot-value *e1* 'email) +=> "lenin@soviet.org" +(update-records [employee] + :av-pairs '(([email] "v.lenin@soviet.org")) + :where [= [emplid] 1]) +=> +(update-instance-from-records *e1*) +=> #<EMPLOYEE {4806B53D}> +(slot-value *e1* 'email) +=> "v.lenin@soviet.org" + + + + Side Effects + + Slot values of object may be modified. + + + + Affected by + + + Data in SQL database. + + + + + Exceptional Situations + + If database is not able to be read. + + + + See Also + + update-slot-from-record + update-objects-joins + + + + Notes + + None. + + + + + + + UPDATE-SLOT-FROM-RECORD + Update objects slot from database. + Function + + + Syntax + + update-slot-from-record object slot &key database => object + + + Arguments and Values + + + object + + + An instance of a View Class. + + + + + slot + + + The name of a slot in object. + + + + + database + + + A database + object. This will default to the value of + *default-database*. + + + + + + + Description + Updates the slot value, specified by the CLOS slot name + slot, of the View Class instance + object using the attribute values of the + appropriate table of database which + defaults to the database associated with + object or, if + object is not associated with a database, + *default-database*. Join slots are updated + but instances of the class on which the join is made are not + updated. + + + + Examples + +(defvar *e1* (car (select 'employee :where [= [slot-value 'employee 'emplid] 1] :flatp t))) +=> *E1* +(slot-value *e1* 'email) +=> "lenin@soviet.org" +(update-records [employee] + :av-pairs '(([email] "v.lenin@soviet.org")) + :where [= [emplid] 1]) +=> +(update-slot-from-record *e1* 'email) +=> #<EMPLOYEE {4806B53D}> +(slot-value *e1* 'email) +=> "v.lenin@soviet.org" + + + + Side Effects + + Modifies the slot value of the object. + + + + Affected by + + + Data in SQL database. + + + + + Exceptional Situations + + Database errors. + + + + See Also + + update-instance-from-records + update-objects-joins + + + + Notes + + None. + + + + + + + UPDATE-OBJECTS-JOINS + Updates joined slots of objects. + Function + + + Syntax + + update-objects-joins objects &key slots force-p class-name max-len => + + + Arguments and Values + + + objects + + + A list of instances of a View Class. + + + + + slots + + * :immediate (default) - refresh join slots with :retrieval :immediate + * :deferred - refresh join slots created with :retrieval :deferred + * :all,t - refresh all join slots regardless of :retrieval + * list of symbols - which explicit slots to refresh + * a single symobl - what slot to refresh + + + + force-p + + + A Boolean, defaulting to &t;. + + + + + class-name + + + A list of instances of a View Class. + + + + + max-len + + + A non-negative integer or &nil; defaulting to + *default-update-objects-max-len*. + When non-nil this is essentially a batch size for the max number of objects + to query from the database at a time. If we need more than max-len + we loop till we have all the objects + + + + + + + Description + + Updates from the records of the appropriate database tables the join slots + specified by SLOTS in the supplied list of + View Class instances OBJECTS. + + A simpler method of causing a join-slot to be requeried is to set it to + unbound, then request it again. This function has efficiency gains where + join-objects are shared among the `objects` (querying all join-objects, + then attaching them appropriately to each of the `objects`) + + + + Examples + +(defvar *addresses* (select 'deferred-employee-address :order-by [ea_join aaddressid] :flatp t)) +=> *ADDRESSES* +(slot-boundp (car *addresses*) 'address) +=> NIL +(update-objects-joins *addresses*) +=> +(slot-boundp (car *addresses*) 'address) +=> T +(slot-value (car *addresses*) 'address) +=> #<ADDRESS {480B0F1D}> + + + + Side Effects + + The slot values of objects are modified. + + + + Affected by + + + *default-update-objects-max-len* + + + + + Exceptional Situations + + Database errors. + + + + See Also + + *default-update-objects-max-len* + update-instance-from-records + update-slot-from-record + + + + Notes + + None. + + + + + diff --git a/doc/ref-prepared.xml b/doc/ref-prepared.xml new file mode 100644 index 0000000..ff922fa --- /dev/null +++ b/doc/ref-prepared.xml @@ -0,0 +1,298 @@ + + +%myents; +]> + + + + + Prepared Statements + + + + + + + + + PREPARE-SQL + + + >PREPARE-SQL + Create a prepared statement. + Function + + + Syntax + + prepare-sql sql-stmt types &key database result-types field-names => result + + + Arguments and Values + + + + + + Description + Prepares a SQL statement sql-stmt + for execution. types contains a list of + types corresponding to the input parameters. Returns a + prepared-statement object. + + A type can be + :int + :double + :null + (:string n) + + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + RUN-PREPARED-SQL + + + RUN-PREPARED-SQL + Execute a prepared statement. + Function + + + Syntax + + run-prepared-sql prepared-stmt => + + + Arguments and Values + + + + + + Description + Execute the prepared sql statment. All input + parameters must be bound. + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + FREE-PREPARED-SQL + + + FREE-PREPARED-SQL + Delete a prepared statement object. + Function + + + Syntax + + free-prepared-sql prepared-stmt => + + + Arguments and Values + + + + + + Description + Delete the objects associated with a prepared + statement. + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + + + BIND-PARAMETER + + + BIND-PARAMETER + Bind a parameter in a prepared statement. + Function + + + Syntax + + bind-parameter prepared-stmt position value => + + + Arguments and Values + + + + + + Description + Sets the value of a parameter in a prepared statement. + + + + Examples + + + + + + Side Effects + + + + + + Affected by + + + + + + + + Exceptional Situations + + + + + + See Also + + + + + + + + Notes + + + + + + + diff --git a/doc/ref-recording.xml b/doc/ref-recording.xml new file mode 100644 index 0000000..fb4fc1c --- /dev/null +++ b/doc/ref-recording.xml @@ -0,0 +1,825 @@ + + +%myents; +]> + + + + + SQL I/O Recording + + + &clsql; provides a facility for recording SQL commands sent to + and/or results returned from the underlying RDBMS to user + sprecified streams. This is useful for monitoring &clsql; + activity and for debugging applications. + + + This section documents the functions provided for enabling and + disabling SQL recording as well as for manipulating the streams + on to which SQL commands and results are recorded. + + + + + + START-SQL-RECORDING + + + START-SQL-RECORDING + Start recording SQL commands or results. + Function + + + Syntax + + start-sql-recording &key type database => + + + Arguments and Values + + + type + + + One of the following keyword symbols: + :commands, :results or + :both, defaulting to + :commands. + + + + database + + A + database + object. This will default to + *default-database*. + + + + + + Description + Starts recording of SQL commands sent to and/or results + returned from database which defaults to + *default-database*. The SQL is output on one or + more broadcast streams, initially just + *standard-output*, and the functions + add-sql-stream and + delete-sql-stream may be used to add or + delete command or result recording streams. The default value of + type is :commands which + means that SQL commands sent to database + are recorded. If type is + :results then SQL results returned from + database are recorded. Both commands and + results may be recorded by passing type + value of :both. + + + + Examples + +(start-sql-recording :type :both) +=> +(select [last-name] :from [employee] + :where [= [emplid] 1] + :field-names nil + :flatp t) +;; 2004-07-02 16:42:12 dent/test/dent => SELECT last_name FROM employee WHERE (emplid = 1) +;; 2004-07-02 16:42:12 dent/test/dent <= (Lenin) +=> ("Lenin") + + + + Side Effects + + The command and result recording broadcast streams associated + with database are reinitialised with + only *standard-output* as their component + streams. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + stop-sql-recording + sql-recording-p + sql-stream + add-sql-stream + delete-sql-stream + list-sql-streams + + + + Notes + + None. + + + + + + + STOP-SQL-RECORDING + + + STOP-SQL-RECORDING + Stop recording SQL commands or results. + Function + + + Syntax + + stop-sql-recording &key type database => + + + Arguments and Values + + + type + + + One of the following keyword symbols: + :commands, :results or + :both, defaulting to + :commands. + + + + database + + A + database + object. This will default to + *default-database*. + + + + + + Description + Stops recording of SQL commands sent to and/or results + returned from database which defaults to + *default-database*. The default value of + type is :commands which + means that SQL commands sent to database + will no longer be recorded. If type is + :results then SQL results returned from + database will no longer be + recorded. Recording may be stopped for both commands and results + by passing type value of + :both. + + + + Examples + +(start-sql-recording :type :both) +=> +(select [last-name] :from [employee] + :where [= [emplid] 1] + :field-names nil + :flatp t) +;; 2004-07-02 16:42:12 dent/test/dent => SELECT last_name FROM employee WHERE (emplid = 1) +;; 2004-07-02 16:42:12 dent/test/dent <= (Lenin) +=> ("Lenin") +(stop-sql-recording :type :results) +=> +(select [last-name] :from [employee] + :where [= [emplid] 1] + :field-names nil + :flatp t) +;; 2004-07-02 16:44:11 dent/test/dent => SELECT last_name FROM employee WHERE (emplid = 1) +=> ("Lenin") + + + + Side Effects + + The command and result recording broadcast streams associated + with database are reinitialised to + &nil;. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + start-sql-recording + sql-recording-p + + + + Notes + + None. + + + + + + + SQL-RECORDING-P + + + SQL-RECORDING-P + Tests whether SQL commands or results are being recorded. + Function + + + Syntax + + sql-recording-p &key type database => result + + + Arguments and Values + + + type + + + One of the following keyword symbols: + :commands, :results, + :both or :either + defaulting to :commands. + + + + + database + + A + database + object. This will default to + *default-database*. + + + + result + + + A Boolean. + + + + + + + Description + Predicate to test whether the SQL recording specified by + type is currently enabled for + database which defaults to + *default-database*. + type may be one of + :commands, :results, + :both or :either, defaulting + to :commands, otherwise &nil; is returned. + + + + Examples + +(start-sql-recording :type :commands) +=> +(sql-recording-p :type :commands) +=> T +(sql-recording-p :type :both) +=> NIL +(sql-recording-p :type :either) +=> T + + + + Side Effects + + None. + + + + Affected by + + start-sql-recording + stop-sql-recording + + + + Exceptional Situations + + None. + + + + See Also + + start-sql-recording + stop-sql-recording + + + + Notes + + The :both and :either values + for the type keyword argument are + &clsql; extensions. + + + + + + + SQL-STREAM + + + SQL-STREAM + Returns the broadcast stream used for recording SQL commands or results. + Function + + + Syntax + + sql-stream &key type database => result + + + Arguments and Values + + + type + + + One of the following keyword symbols: + :commands or :results, + defaulting to :commands. + + + + database + + A + database + object. This will default to + *default-database*. + + + + result + + + A broadcast stream or &nil;. + + + + + + + Description + Returns the broadcast stream used for recording SQL + commands sent to or results returned from + database which defaults to + *default-database*. type + must be one of :commands or + :results, defaulting to + :commands, and determines whether the stream + returned is that used for recording SQL commands or results. + + + + Examples + +(start-sql-recording :type :commands) +=> +(sql-stream :type :commands) +=> #<Broadcast Stream> +(sql-stream :type :results) +=> NIL + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + An error is signalled if type is not + one of :commands or + :results. + + + + See Also + + start-sql-recording + add-sql-stream + delete-sql-stream + list-sql-streams + + + + Notes + + None. + + + + + + + ADD-SQL-STREAM + + + ADD-SQL-STREAM + Add a component to the broadcast streams used for recording SQL commands or results. + Function + + + Syntax + + add-sql-stream stream &key type database => result + + + Arguments and Values + + + stream + + + A stream or &t;. + + + + + type + + + One of the following keyword symbols: + :commands, :results or + :both, defaulting to + :commands. + + + + + database + + A + database + object. This will default to + *default-database*. + + + + result + + + The added stream. + + + + + + + Description + Adds the supplied stream stream (or + &t; for *standard-output*) as a component of + the recording broadcast stream for the SQL recording type + specified by type on + database which defaults to + *default-database*. type + must be one of :commands, + :results, or :both, defaulting + to :commands, depending on whether the stream + is to be added for recording SQL commands, results or both. + + + + Examples + +(start-sql-recording :type :commands) +=> +(with-output-to-string (s) + (add-sql-stream s :type :commands) + (print-query [select [emplid] [first-name] [last-name] [email] :from [employee]] + :stream s)) + +;; 2004-07-02 17:38:45 dent/test/dent => SELECT emplid,first_name,last_name,email FROM employee +=> +";; 2004-07-02 17:38:45 dent/test/dent => SELECT emplid,first_name,last_name,email FROM employee +1 Vladimir Lenin lenin@soviet.org +2 Josef Stalin stalin@soviet.org +3 Leon Trotsky trotsky@soviet.org +4 Nikita Kruschev kruschev@soviet.org +5 Leonid Brezhnev brezhnev@soviet.org +6 Yuri Andropov andropov@soviet.org +7 Konstantin Chernenko chernenko@soviet.org +8 Mikhail Gorbachev gorbachev@soviet.org +9 Boris Yeltsin yeltsin@soviet.org +10 Vladimir Putin putin@soviet.org " + + + + Side Effects + + The specified broadcast stream(s) associated with + database are modified. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + start-sql-recording + sql-stream + delete-sql-stream + list-sql-streams + + + + Notes + + None. + + + + + + + DELETE-SQL-STREAM + + + DELETE-SQL-STREAM + Remove a component from the broadcast streams used for recording SQL commands or results. + Function + + + Syntax + + delete-sql-stream stream &KEY type database => result + + + Arguments and Values + + + stream + + + A stream or &t;. + + + + + stream + + + A stream or &t;. + + + + + type + + + One of the following keyword symbols: + :commands, :results or + :both, defaulting to + :commands. + + + + + database + + A + database + object. This will default to + *default-database*. + + + + result + + + The added stream. + + + + + + + Description + Removes the supplied stream stream + from the recording broadcast stream for the SQL recording type + specified by type on + database which defaults to + *default-database*. type + must be one of :commands, + :results, or :both, defaulting + to :commands, depending on whether the stream + is to be added for recording SQL commands, results or both. + + + + Examples + +(list-sql-streams :type :both) +=> (#<Stream for descriptor 7> #<Stream for descriptor 7>) +(delete-sql-stream *standard-output* :type :results) +=> #<Stream for descriptor 7> +(list-sql-streams :type :both) +=> (#<Stream for descriptor 7>) + + + + Side Effects + + The specified broadcast stream(s) associated with + database are modified. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + start-sql-recording + stop-sql-recording + sql-recording-p + sql-stream + add-sql-stream + delete-sql-stream + list-sql-streams + + + + Notes + + None. + + + + + + + LIST-SQL-STREAMS + + + LIST-SQL-STREAMS + List the components of the broadcast streams used for recording SQL commands or results. + Function + + + Syntax + + list-sql-streams &key type database => result + + + Arguments and Values + + + type + + + One of the following keyword symbols: + :commands, :results or + :both, defaulting to + :commands. + + + + + database + + A + database + object. This will default to + *default-database*. + + + + result + + + A list. + + + + + + + Description + Returns the list of component streams for the broadcast + stream recording SQL commands sent to and/or results returned + from database which defaults to + *default-database*. type + must be one of :commands, + :results, or :both, defaulting + to :commands, and determines whether the listed + streams contain those recording SQL commands, results or both. + + + + Examples + +(list-sql-streams :type :both) +=> NIL +(start-sql-recording :type :both) +=> +(list-sql-streams :type :both) +=> (#<Stream for descriptor 7> #<Stream for descriptor 7>) + + + + Side Effects + + None. + + + + Affected by + + + add-sql-stream + delete-sql-stream + + + + + Exceptional Situations + + An error is signalled if type is passed + a value other than :commands, + :results or :both. + + + + See Also + + sql-stream + add-sql-stream + delete-sql-stream + + + + Notes + + None. + + + + + diff --git a/doc/ref-syntax.xml b/doc/ref-syntax.xml new file mode 100644 index 0000000..e0364fa --- /dev/null +++ b/doc/ref-syntax.xml @@ -0,0 +1,1058 @@ + + +%myents; +]> + + + + The Symbolic SQL Syntax + + + &clsql; provides a symbolic syntax allowing the construction of + SQL expressions as lists delimited by square brackets. The + syntax is turned off by default. This section describes + utilities for enabling and disabling the square bracket reader + syntax and for constructing symbolic SQL expressions. + + + Tip: just want it on + + file-enable-sql-reader-syntax at the top of each file is easiest. + + + + + + + ENABLE-SQL-READER-SYNTAX + + + ENABLE-SQL-READER-SYNTAX + Globally enable square bracket reader syntax. + Macro + + + Syntax + + enable-sql-reader-syntax => + + + Arguments and Values + None. + + + Description + Turns on the SQL reader syntax setting the syntax state + such that if the syntax is subsequently disabled, + restore-sql-reader-syntax-state will + enable it again. + + + + Examples + None. + + + Side Effects + + Sets the internal syntax state to enabled. + + + Modifies the default readtable. + + + + &clsql; tries to keep track of whether the syntax has already been enabled. This can be problematic if the syntax is somehow disabled externally to &clsql; as future attempts to enable the syntax will do nothing--the system thinks it is already enabled. This may happen if there is an enable, but no disable, in a file that is processed with load or compile-file as the lisp implementation will restore the readtable on completion. Or, even if there is a disable but a compiler-error is encountered before running the disable. If you encounter this try running disable-sql-reader-syntax a couple times in the REPL. + + See file-enable-sql-reader-syntax for an alternative. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + disable-sql-reader-syntax + locally-enable-sql-reader-syntax + locally-disable-sql-reader-syntax + restore-sql-reader-syntax-state + file-enable-sql-reader-syntax + + + + Notes + + The symbolic SQL syntax is disabled by default. + + + &clsql; differs from &commonsql; in that + enable-sql-reader-syntax is defined as a + macro rather than a function. + + + + + + + DISABLE-SQL-READER-SYNTAX + + + DISABLE-SQL-READER-SYNTAX + Globally disable square bracket reader syntax. + Macro + + + Syntax + + disable-sql-reader-syntax => + + + Arguments and Values + None. + + + Description + Turns off the SQL reader syntax setting the syntax state + such that if the syntax is subsequently enabled, + + restore-sql-reader-syntax-state will + disable it again. + + + + Examples + None. + + + Side Effects + + Sets the internal syntax state to disabled. + + + Modifies the default readtable. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + enable-sql-reader-syntax + locally-enable-sql-reader-syntax + locally-disable-sql-reader-syntax + restore-sql-reader-syntax-state + file-enable-sql-reader-syntax + + + + Notes + + The symbolic SQL syntax is disabled by default. + + + &clsql; differs from &commonsql; in that + disable-sql-reader-syntax is defined as a + macro rather than a function. + + + + + + + LOCALLY-ENABLE-SQL-READER-SYNTAX + + + LOCALLY-ENABLE-SQL-READER-SYNTAX + Locally enable square bracket reader syntax. + Macro + + + Syntax + + locally-enable-sql-reader-syntax => + + + Arguments and Values + None. + + + Description + Turns on the SQL reader syntax without changing the syntax + state such that + restore-sql-reader-syntax-state will + re-establish the current syntax state. + + + + Examples + Intended to be used in a file for code which uses the + square bracket syntax without changing the global state. + + + #.(locally-enable-sql-reader-syntax) + + ... CODE USING SYMBOLIC SQL SYNTAX ... + + #.(restore-sql-reader-syntax-state) + + + + Side Effects + + Modifies the default readtable. + + + + &clsql; tries to keep track of whether the syntax has already been enabled. This can be problematic if the syntax is somehow disabled externally to &clsql; as future attempts to enable the syntax will do nothing--the system thinks it is already enabled. This may happen if there is an enable, but no disable, in a file that is processed with load or compile-file as the lisp implementation will restore the readtable on completion. Or, even if there is a disable but a compiler-error is encountered before running the disable. If you encounter this try running disable-sql-reader-syntax a couple times in the REPL. + + See file-enable-sql-reader-syntax for an alternative. + + + + Affected by + None. + + + Exceptional Situations + + None. + + + + See Also + + enable-sql-reader-syntax + disable-sql-reader-syntax + locally-disable-sql-reader-syntax + restore-sql-reader-syntax-state + file-enable-sql-reader-syntax + + + + Notes + + The symbolic SQL syntax is disabled by default. + + + &clsql; differs from &commonsql; in that + locally-enable-sql-reader-syntax is + defined as a macro rather than a function. + + + + + + + + LOCALLY-DISABLE-SQL-READER-SYNTAX + + + LOCALLY-DISABLE-SQL-READER-SYNTAX + Locally disable square bracket reader syntax. + Macro + + + Syntax + + locally-disable-sql-reader-syntax => + + + Arguments and Values + None. + + + Description + Turns off the SQL reader syntax without changing the + syntax state such that + restore-sql-reader-syntax-state will + re-establish the current syntax state. + + + + Examples + Intended to be used in a file for code in which the square + bracket syntax should be disabled without changing the global + state. + + + #.(locally-disable-sql-reader-syntax) + + ... CODE NOT USING SYMBOLIC SQL SYNTAX ... + + #.(restore-sql-reader-syntax-state) + + + + Side Effects + + Modifies the default readtable. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + enable-sql-reader-syntax + disable-sql-reader-syntax + locally-enable-sql-reader-syntax + restore-sql-reader-syntax-state + file-enable-sql-reader-syntax + + + + Notes + + The symbolic SQL syntax is disabled by default. + + + &clsql; differs from &commonsql; in that + locally-disable-sql-reader-syntax is + defined as a macro rather than a function. + + + + + + + RESTORE-SQL-READER-SYNTAX-STATE + + + RESTORE-SQL-READER-SYNTAX-STATE + + Restore square bracket reader syntax to its previous state. + + Macro + + + Syntax + + restore-sql-reader-syntax-state => + + + Arguments and Values + None. + + + Description + Enables the SQL reader syntax if + enable-sql-reader-syntax has been + called more recently than + disable-sql-reader-syntax and + otherwise disables the SQL reader syntax. By default, the SQL + reader syntax is disabled. + + + + Examples + + See + locally-enable-sql-reader-syntax and + + locally-disable-sql-reader-syntax. + + + + Side Effects + + Reverts the internal syntax state. + + + Modifies the default readtable. + + + + Affected by + The current internal syntax state. + + + Exceptional Situations + + None. + + + + See Also + + enable-sql-reader-syntax + disable-sql-reader-syntax + locally-enable-sql-reader-syntax + locally-disable-sql-reader-syntax + file-enable-sql-reader-syntax + + + + Notes + + The symbolic SQL syntax is disabled by default. + + + &clsql; differs from &commonsql; in that + restore-sql-reader-syntax-state is + defined as a macro rather than a function. + + + + + + + FILE-ENABLE-SQL-READER-SYNTAX + + + FILE-ENABLE-SQL-READER-SYNTAX + + Enable the square bracket reader syntax for the duration of the file. + + Macro + + + Syntax + + file-enable-sql-reader-syntax => + + + Arguments and Values + None. + + + Description + Uncoditionally enables the SQL reader syntax. Unlike + enable-sql-reader-syntax and + disable-sql-reader-syntax which try to keep track of whether + the syntax has been enabled or disabled and keep track of the old read-table for restoration this function just enables it unconditionally. + + Once enabled this way there is no corresponding disable function but instead relies on being used in a file context. The spec for load and compile-file states that the *readtable* will be restored after processing the file. + + + Examples + Intended to be used at the top of a file that contains sql reader syntax. + + (in-package :my-package) + (clsql:file-enable-sql-reader-syntax) + ... + ;;functions that use the square bracket syntax. + + + + Side Effects + + Modifies the readtable for #\[ and #\] + + + + Affected by + None. + + + Exceptional Situations + + None. + + + + See Also + + enable-sql-reader-syntax + disable-sql-reader-syntax + locally-enable-sql-reader-syntax + locally-disable-sql-reader-syntax + + + + Notes + + Unique to &clsql;, not present in &commonsql;. + + + + + + + SQL + + + SQL + Construct an SQL string from supplied expressions. + Function + + + Syntax + + sql &rest args => sql-expression + + + Arguments and Values + + + args + + A set of expressions. + + + + sql-expression + + A string representing an SQL expression. + + + + + + Description + Returns an SQL string generated from the expressions + args. The expressions are translated into + SQL strings and then concatenated with a single space delimiting + each expression. + + + + Examples + +(sql nil) +=> "NULL" + +(sql 'foo) +=> "FOO" + +(sql "bar") + => "'bar'" + +(sql 10) +=> "10" + +(sql '(nil foo "bar" 10)) +=> "(NULL,FOO,'bar',10)" + +(sql #(nil foo "bar" 10)) +=> "NULL,FOO,'bar',10" + +(sql [select [foo] [bar] :from [baz]] 'having [= [foo id] [bar id]] + 'and [foo val] '< 5) +=> "SELECT FOO,BAR FROM BAZ HAVING (FOO.ID = BAR.ID) AND FOO.VAL < 5" + + + + Side Effects + None. + + + Affected by + + None. + + + + Exceptional Situations + An error of type sql-user-error + is signalled if any element in args is + not of the supported types (a symbol, string, number or symbolic + SQL expression) or a list or vector containing only these + supported types. + + + + See Also + + sql-expression + sql-operation + sql-operator + + + + Notes + None. + + + + + + SQL-EXPRESSION + + + SQL-EXPRESSION + Constructs an SQL expression from supplied keyword arguments. + Function + + + Syntax + + sql-expression &key string table alias attribute type => result + + + Arguments and Values + + + string + + A string. + + + + table + + A symbol representing a database table identifier. + + + + alias + + A table alias. + + + + attribute + + A symbol representing an attribute identifier. + + + + type + + A type specifier. + + + + result + + A object of type sql-expression. + + + + + + Description + Returns an SQL expression constructed from the supplied + arguments which may be combined as follows: + + + + attribute and + type; + + + + + attribute; + + + + + alias or table and + attribute and + type; + + + + + alias or + table and + attribute; + + + + + table, + attribute and + type; + + + + + table and + attribute; + + + + + table + and alias; + + + + + table; + + + + + string. + + + + + + Examples + +(sql-expression :table 'foo :attribute 'bar) +=> #<CLSQL-SYS:SQL-IDENT-ATTRIBUTE FOO.BAR> + +(sql-expression :attribute 'baz) +=> #<CLSQL-SYS:SQL-IDENT-ATTRIBUTE BAZ> + + + + Side Effects + None. + + + Affected by + + None. + + + + Exceptional Situations + An error of type sql-user-error + is signalled if an unsupported combination of keyword arguments is + specified. + + + + See Also + + sql + sql-operation + sql-operator + + + + Notes + None. + + + + + + SQL-OPERATION + + + SQL-OPERATION + Constructs an SQL expression from a supplied operator and arguments. + Function + + + Syntax + + sql-operation operator &rest args => result + + sql-operation 'function func &rest args => result + + + Arguments and Values + + + operator + + A symbol denoting an SQL operator. + + + + func + + A string denoting an SQL function. + + + + args + + A set of arguments for the specified SQL operator or function. + + + + result + + A object of type sql-expression. + + + + + + Description + Returns an SQL expression constructed from the supplied + SQL operator or function operator and + its arguments args. If + operator is passed the symbol 'function + then the first value in args is taken to + be a valid SQL function and the remaining values in + args its arguments. + + + + Examples + +(sql-operation 'select + (sql-expression :table 'foo :attribute 'bar) + (sql-operation 'sum (sql-expression :table 'foo :attribute 'baz)) + :from + (sql-expression :table 'foo) + :where + (sql-operation '> (sql-expression :attribute 'bar) 12) + :order-by (sql-operation 'sum (sql-expression :attribute 'baz))) +=> #<SQL-QUERY SELECT FOO.BAR,SUM(FOO.BAZ) FROM FOO WHERE (BAR > 12) ORDER BY SUM(BAZ)> + +(sql-operation 'function "strpos" "CLSQL" "SQL") +=> #<CLSQL-SYS:SQL-FUNCTION-EXP STRPOS('CLSQL','SQL')> + + + + Side Effects + None. + + + Affected by + + None. + + + + Exceptional Situations + An error of type sql-user-error + is signalled if operator is not a symbol + representing a supported SQL operator. + + + See Also + + sql + sql-expression + sql-operator + + + + Notes + None. + + + + + + SQL-OPERATOR + + + SQL-OPERATOR + Returns the symbol for the supplied SQL operator. + Function + + + Syntax + + sql-operator operator => result + + + Arguments and Values + + + operator + + A symbol denoting an SQL operator. + + + + result + + The Lisp symbol used by &clsql; to represent the + specified operator. + + + + + + Description + Returns the Lisp symbol corresponding to the SQL operator + represented by the symbol operator. If + operator does not represent a supported + SQL operator or is not a symbol, nil is returned. + + + + Examples + +(sql-operator 'like) +=> SQL-LIKE + + + + Side Effects + None. + + + Affected by + + None. + + + + Exceptional Situations + None. + + + See Also + + sql + sql-expression + sql-operation + + + + Notes + + &clsql;'s symbolic SQL syntax currently has support for the + following &commonsql; compatible SQL operators: + + + + any + some + all + not + union + intersect + minus + except + + order-by + + null + + * + + + + + / + + - + + like + + and + + or + + in + + substr + + || + + = + + < + + > + + >= + + <= + + <> + + count + + max + + min + + avg + + sum + + function + + between + + distinct + + nvl + + slot-value + + userenv + + + + as well as the pseudo-operator function. + + The following operators are provided as &clsql; extensions to + the &commonsql; API. + + + concat + + substring + + limit + + group-by + + having + + not-null + + exists + + uplike + + is + + == + + the + + coalesce + + view-class + + + + + Note that some of these operators are not supported by all of + the RDBMS supported by &clsql; (see the Appendix for details). + + + + + diff --git a/doc/ref-transaction.xml b/doc/ref-transaction.xml new file mode 100644 index 0000000..5731c46 --- /dev/null +++ b/doc/ref-transaction.xml @@ -0,0 +1,844 @@ + + +%myents; +]> + + + + + Transaction Handling + + + This section describes the interface provided by &clsql; for + handling database transactions. The interface allows for opening + transaction blocks, committing or rolling back changes made and + controlling autocommit behaviour. + + + + In contrast to &commonsql;, &clsql;, by default, starts in + transaction AUTOCOMMIT mode (see set-autocommit). + To begin a transaction in autocommit mode, start-transaction + has to be called explicitly. + + + + + + + START-TRANSACTION + + + START-TRANSACTION + Open a transaction block. + Function + + + Syntax + + start-transaction &key database => &nil; + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + Starts a transaction block on + database which defaults to + *default-database* and which continues until + rollback or commit are + called. + + + + Examples + +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> NIL +(start-transaction) +=> NIL +(in-transaction-p) +=> T +(insert-records :into [foo] :av-pairs '(([bar] 1) ([baz] "one"))) +=> +(select [*] :from [foo] :field-names nil) +=> ((1 "one")) +(rollback) +=> NIL +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> NIL + + + + Side Effects + + Autocommit mode is disabled and if + database is currently within the scope + of a transaction, all commit and rollback hooks are removed + and the transaction level associated with + database is modified. + + + + Affected by + + None. + + + + Exceptional Situations + + Signals an error of type sql-database-error + if database is not a database object. + + + + See Also + + commit + rollback + in-transaction-p + set-autocommit + with-transaction + + + + Notes + + start-transaction is a &clsql; extension. + + + + + + + COMMIT + + + COMMIT + Commit modifications made in the current transaction. + Function + + + Syntax + + commit &key database => &nil; + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + If database, which defaults to + *default-database*, is currently within the + scope of a transaction, commits changes made since the + transaction began. + + + + Examples + +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> NIL +(start-transaction) +=> NIL +(in-transaction-p) +=> T +(insert-records :into [foo] :av-pairs '(([bar] 1) ([baz] "one"))) +=> +(select [*] :from [foo] :field-names nil) +=> ((1 "one")) +(commit) +=> NIL +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> ((1 "one")) + + + + Side Effects + + Changes made within the scope of the current transaction are + committed in the underlying database and the transaction level + of database is reset. + + + + Affected by + + The transaction level of database which + indicates whether a transaction has been initiated by a call to + start-transaction since the last call to + rollback or commit. + + + + Exceptional Situations + + Signals an error of type sql-database-error + if database is not a database object. A + warning of type sql-warning is signalled if there + is no transaction in progress. + + + + See Also + + start-transaction + rollback + in-transaction-p + add-transaction-commit-hook + set-autocommit + with-transaction + + + + Notes + + None. + + + + + + + ROLLBACK + + + ROLLBACK + Roll back modifications made in the current transaction. + Function + + + Syntax + + rollback &key database => &nil; + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + + + Description + If database, which defaults to + *default-database*, is currently within the + scope of a transaction, rolls back changes made since the + transaction began. + + + + Examples + +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> NIL +(start-transaction) +=> NIL +(in-transaction-p) +=> T +(insert-records :into [foo] :av-pairs '(([bar] 1) ([baz] "one"))) +=> +(select [*] :from [foo] :field-names nil) +=> ((1 "one")) +(rollback) +=> NIL +(in-transaction-p) +=> NIL +(select [*] :from [foo] :field-names nil) +=> NIL + + + + Side Effects + + Changes made within the scope of the current transaction are + reverted in the underlying database and the transaction level + of database is reset. + + + Affected by + + The transaction level of database which + indicates whether a transaction has been initiated by a call to + start-transaction since the last call to + rollback or commit. + + + + Exceptional Situations + + Signals an error of type sql-database-error + if database is not a database object. A + warning of type sql-warning is signalled if + there is no transaction in progress. + + + + See Also + + start-transaction + commit + in-transaction-p + add-transaction-rollback-hook + set-autocommit + with-transaction + + + + Notes + + None. + + + + + + + IN-TRANSACTION-P + + + IN-TRANSACTION-P + A predicate for testing whether a transaction is currently in progress. + Function + + + Syntax + + in-transaction-p &key database => result + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result + + A Boolean. + + + + + + Description + A predicate to test whether + database, which defaults to + *default-database*, is currently within the + scope of a transaction. + + + + Examples + +(in-transaction-p) +=> NIL +(start-transaction) +=> NIL +(in-transaction-p) +=> T +(commit) +=> NIL +(in-transaction-p) +=> NIL + + + + Side Effects + + None. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + start-transaction + commit + rollback + set-autocommit + + + + Notes + + in-transaction-p is a &clsql; extension. + + + + + + + ADD-TRANSACTION-COMMIT-HOOK + + + ADD-TRANSACTION-COMMIT-HOOK + Specify hooks to be run when committing changes. + Function + + + Syntax + + add-transaction-commit-hook commit-hook &key database => result + + + Arguments and Values + + + commit-hook + + A designator for a function with no required arguments. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result + + The list of currently defined commit hooks for + database. + + + + + + + Description + + Adds commit-hook, which should a + designator for a function with no required arguments, to the + list of hooks run when commit is called + on database which defaults to + *default-database*. + + + + Examples + +(start-transaction) +=> NIL +(add-transaction-commit-hook #'(lambda () (print "Successfully committed."))) +=> (#<Interpreted Function (LAMBDA # #) {48E2E689}>) +(commit) +"Successfully committed." +=> NIL + + + + Side Effects + + commit-hook is added to the list of + commit hooks for database. + + + + Affected by + + None. + + + + Exceptional Situations + + If commit-hook has one or more required + arguments, an error will be signalled when + commit is called. + + + + See Also + + commit + rollback + add-transaction-rollback-hook + with-transaction + + + Notes + + add-transaction-commit-hook is a &clsql; extension. + + + + + + + ADD-TRANSACTION-ROLLBACK-HOOK + + + ADD-TRANSACTION-ROLLBACK-HOOK + Specify hooks to be run when rolling back changes. + Function + + + Syntax + + add-transaction-rollback-hook rollback-hook &key database => result + + + Arguments and Values + + + rollback-hook + + A designator for a function with no required arguments. + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result + + The list of currently defined rollback hooks for + database. + + + + + + + Description + + Adds rollback-hook, which should a + designator for a function with no required arguments, to the + list of hooks run when rollback is called + on database which defaults to + *default-database*. + + + Examples + +(start-transaction) +=> NIL +(add-transaction-rollback-hook #'(lambda () (print "Successfully rolled back."))) +=> (#<Interpreted Function (LAMBDA # #) {48E37C31}>) +(rollback) +"Successfully rolled back." +=> NIL + + + + Side Effects + + rollback-hook is added to the list of + rollback hooks for database. + + + + Affected by + + None. + + + + Exceptional Situations + + If rollback-hook has one or more + required arguments, an error will be signalled when + rollback is called. + + + + See Also + + commit + rollback + add-transaction-commit-hook + + + + Notes + + add-transaction-rollback-hook is a &clsql; extension. + + + + + + + SET-AUTOCOMMIT + + + SET-AUTOCOMMIT + Turn on or off autocommit for a database. + Function + + + Syntax + + set-autocommit value &key database => result + + + Arguments and Values + + + value + + A Boolean specifying the desired autocommit + behaviour for database. + + + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + result + + The previous autocommit value for + database. + + + + + + + Description + Turns autocommit off for database + if value is &nil;, and otherwise turns it + on. Returns the old value of autocommit flag. + + + For RDBMS (such as Oracle) which don't automatically commit + changes, turning autocommit on has the effect of explicitly + committing changes made whenever SQL statements are executed. + + + Autocommit is turned on by default. + + + + Examples + + + + + + Side Effects + + database is associated with the specified + autocommit mode. + + + + Affected by + + None. + + + + Exceptional Situations + + None. + + + + See Also + + start-transaction + commit + add-transaction-commit-hook + with-transaction + + + + Notes + + set-autocommit is a &clsql; extension. + + + + + + + WITH-TRANSACTION + + + WITH-TRANSACTION + Execute a body of code within a transaction. + Macro + + + Syntax + + with-transaction &key database &rest body => result + + + Arguments and Values + + + database + + A + database + object. This will default to the value + of *default-database*. + + + + body + + + A body of Lisp code. + + + + + result + + The result of executing body. + + + + + + Description + Starts a transaction in the database specified by + database, which is + *default-database* by default, and executes + body within that transaction. If + body aborts or throws, + database is rolled back and otherwise the + transaction is committed. + + + + Examples + +(in-transaction-p) +=> NIL +(select [email] :from [employee] :where [= [emplid] 1] :flatp t :field-names nil) +=> ("lenin@soviet.org") +(with-transaction () + (update-records [employee] + :av-pairs '((email "lenin-nospam@soviet.org")) + :where [= [emplid] 1])) +=> NIL +(select [email] :from [employee] :where [= [emplid] 1] :flatp t :field-names nil) +=> ("lenin-nospam@soviet.org") +(in-transaction-p) +=> NIL + + + + Side Effects + + Changes specified in body may be made + to the underlying database if body + completes successfully. + + + + Affected by + + None. + + + + Exceptional Situations + + Signals an error of type sql-database-error + if database is not a database object. + + + + See Also + + start-transaction + commit + rollback + add-transaction-commit-hook + add-transaction-rollback-hook + + + + Notes + + None. + + + + + + diff --git a/doc/schemas.xml b/doc/schemas.xml new file mode 100644 index 0000000..018eb6f --- /dev/null +++ b/doc/schemas.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/threading-warnings.txt b/doc/threading-warnings.txt new file mode 100644 index 0000000..6f6a528 --- /dev/null +++ b/doc/threading-warnings.txt @@ -0,0 +1,77 @@ +============= suggested addition to documentation =============== +== Threads and thread safety + +In a Lisp in which threading is internal, rather than done using OS threads, CLSQL should +be threadsafe. Thus what follows will consider the case of Lisp threads also being OS +threads. + +CLSQL attempts to be thread-safe, with important exceptions. The database pool used by +CONNECT and WITH-DATABASE performs appropriate locking, so that +it is possible to use pooled connections from different threads. As long as database +objects (representing connections) are not passed among threads, WITH-DATABASE +and CONNECT may be used by multiple threads. + +However, the database object contained in the VIEW-DATABASE slot of a STANDARD-DB-OBJECT +persists even after the database is returned to the pool. Thus it is possible for one +thread to read an object, return the database to pool, and then still possess a copy of +the database inside the VIEW-DATABASE slot. Then UPDATE-RECORDS-FOR-INSTANCE, +UPDATE-RECORD-FROM-SLOT, and UPDATE-RECORD-FROM-SLOTS +(which always use the VIEW-DATABASE slot, if not NIL, rather than any supplied +keyword argument DATABASE) can use the database even as a second thread as +retrieved it from a pool, resulting in serious problems. Automatic updating using the internal +VIEW-DATABASE slot also may be triggered by the global variable *DB-AUTO-SYNC*. + +This problem may been addressed by defining a new object class, and changing the method +used to select the database during UPDATE-RECORDS-FOR-INSTANCE, etc. + +;; define a threadsafe child class of STANDARD-DB-OBJECT +(defclass clsql::threadsafe-db-obj (clsql-sys:standard-db-object) nil + (:metaclass clsql-sys::standard-db-class)) + +;; for this class, define threadsafe database chooser method that never uses the +;; internal VIEW-DATABASE slot +(defmethod clsql-sys::choose-database-for-instance + ((object clsql::threadsafe-db-obj) &optional database) + (or database clsql-sys:*default-database* + (signal-no-database-error nil))) + +;; define a new sql database table that should be threadsafely UPDATE-able +(clsql-sys:def-view-class my-table (clsql::threadsafe-db-obj) + (…)) + + +Alternatively, users may redefine *DB-POOL* and *DB-POOL-LOCK* on a per-thread basis +using LET before entering the thread, which will prevent any cross-thread sharing +of connections, possibly at the cost of having more connections. [** is this a valid approach **] +*DB-POOL-LOCK* no longer necessary, however, if connection pools are per-thread. + + +==== Thread safety issues for the back-ends [** my best understanding **] + +* sqlite2 - sqlite2 is not threadsafe. + +* sqlite3 - sqlite3 after and including 3.3.1 is threadsafe if compiled in the default manner. According to sqlite3 documentation, + connections may be moved across threads if and only if no transaction is pending and all statements have been finalized. + +* mysql - the mysql interface is missing initializations required for thread safety: 1) mysql_library_init() is not called in a multithreaded environment; + 2) mysql_thread_init() is not called on a per-thread basis; and 3) mysql_thread_end() is not called before a thread terminates. + The second item may lead to corruption according to mysql documentation, and the third item + leads to memory leaks. + + Another issue with mysql is that it resets sigpipe in a way that renders SBCL unresponsive to interrupts, requiring additional saving and + restoring of the signal handler. [** an example? **] + + Nevertheless, the present version of the mysql back end often works successfully even in a threaded environment, albeit with subtle problems. + + +* postgreSQL - is probably threadsafe [No information] + +* postgreSQL socket - Is probably threadsafe -- has been used for a while without any observed errors. + +* ODBC - the Clsql side doesn't have additional issues beyond what's documented above. But this depends on what odbc driver your using. It appears to work with unixodbc and freetds. + +* AODBC - no information + +* Oracle - no information + +============= end of suggested addition to documentation =============== diff --git a/doc/xinclude.mod b/doc/xinclude.mod new file mode 100644 index 0000000..94c13b6 --- /dev/null +++ b/doc/xinclude.mod @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/examples/clsql-tutorial.lisp b/examples/clsql-tutorial.lisp new file mode 100644 index 0000000..1980e3d --- /dev/null +++ b/examples/clsql-tutorial.lisp @@ -0,0 +1,196 @@ +(asdf:operate 'asdf:load-op 'clsql) + +(in-package #:clsql-user) + +;; You must set these variables to appropriate values. +(defvar *tutorial-database-type* nil + "Possible values are :postgresql :postgresql-socket, :mysql, +:oracle, :odbc, :aodbc or :sqlite") +(defvar *tutorial-database-name* "clsqltut" + "The name of the database we will work in.") +(defvar *tutorial-database-user* "" + "The name of the database user we will work as.") +(defvar *tutorial-database-server* "" + "The name of the database server if required") +(defvar *tutorial-database-password* "" + "The password if required") + +(clsql:def-view-class employee () + ((emplid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :emplid) + (first-name + :accessor first-name + :type (string 30) + :initarg :first-name) + (last-name + :accessor last-name + :type (string 30) + :initarg :last-name) + (email + :accessor employee-email + :type (string 100) + :initarg :email) + (companyid + :type integer + :initarg :companyid) + (company + :accessor employee-company + :db-kind :join + :db-info (:join-class company + :home-key companyid + :foreign-key companyid + :set nil)) + (managerid + :type integer + :initarg :managerid) + (manager + :accessor employee-manager + :db-kind :join + :db-info (:join-class employee + :home-key managerid + :foreign-key emplid + :set nil))) + (:base-table employee)) + +(clsql:def-view-class company () + ((companyid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :companyid) + (name + :type (string 100) + :initarg :name) + (presidentid + :type integer + :initarg :presidentid) + (president + :reader president + :db-kind :join + :db-info (:join-class employee + :home-key presidentid + :foreign-key emplid + :set nil)) + (employees + :reader company-employees + :db-kind :join + :db-info (:join-class employee + :home-key companyid + :foreign-key companyid + :set t))) + (:base-table company)) + +;; Connect to the database (see the CLSQL documentation for vendor +;; specific connection specs). +(case *tutorial-database-type* + ((:mysql :postgresql :postgresql-socket) + (clsql:connect `(,*tutorial-database-server* + ,*tutorial-database-name* + ,*tutorial-database-user* + ,*tutorial-database-password*) + :database-type *tutorial-database-type*)) + ((:odbc :aodbc :oracle) + (clsql:connect `(,*tutorial-database-name* + ,*tutorial-database-user* + ,*tutorial-database-password*) + :database-type *tutorial-database-type*)) + (:sqlite + (clsql:connect `(,*tutorial-database-name*) + :database-type *tutorial-database-type*))) + +;; Record the sql going out, helps us learn what is going +;; on behind the scenes +(clsql:start-sql-recording) + +;; Create the tables for our view classes +;; First we drop them, ignoring any errors +(ignore-errors + (clsql:drop-view-from-class 'employee) + (clsql:drop-view-from-class 'company)) + +(clsql:create-view-from-class 'employee) +(clsql:create-view-from-class 'company) + + +;; Create some instances of our view classes +(defvar company1 (make-instance 'company + :companyid 1 + :name "Widgets Inc." + ;; Lenin is president of Widgets Inc. + :presidentid 1)) + +(defvar employee1 (make-instance 'employee + :emplid 1 + :first-name "Vladamir" + :last-name "Lenin" + :email "lenin@soviet.org" + :companyid 1)) + +(defvar employee2 (make-instance 'employee + :emplid 2 + :first-name "Josef" + :last-name "Stalin" + :email "stalin@soviet.org" + :companyid 1 + ;; Lenin manages Stalin (for now) + :managerid 1)) + +(clsql:update-records-from-instance employee1) +(clsql:update-records-from-instance employee2) +(clsql:update-records-from-instance company1) + +;; lets use the functional sql interface +(clsql:locally-enable-sql-reader-syntax) + +(format t "The email address of ~A ~A is ~A" + (first-name employee1) + (last-name employee1) + (employee-email employee1)) + +(setf (employee-email employee1) "lenin-nospam@soviets.org") + +;; Update the database +(clsql:update-records-from-instance employee1) + +(let ((new-lenin (car + (clsql:select 'employee + :where [= [slot-value 'employee 'emplid] 1] + :flatp t)))) + (format t "His new email is ~A" + (employee-email new-lenin))) + + +;; Some queries + +;; all employees +(clsql:select 'employee) +;; all companies +(clsql:select 'company) + +;; employees named Lenin +(clsql:select 'employee :where [= [slot-value 'employee 'last-name] + "Lenin"]) + +(clsql:select 'company :where [= [slot-value 'company 'name] + "Widgets Inc."]) + +;; Employees of Widget's Inc. +(clsql:select 'employee + :where [and [= [slot-value 'employee 'companyid] + [slot-value 'company 'companyid]] + [= [slot-value 'company 'name] + "Widgets Inc."]]) + +;; Same thing, except that we are using the employee +;; relation in the company view class to do the join for us, +;; saving us the work of writing out the SQL! +(company-employees company1) + +;; President of Widgets Inc. +(president company1) + +;; Manager of Josef Stalin +(employee-manager employee2) diff --git a/examples/dot.clsql-test.config b/examples/dot.clsql-test.config new file mode 100644 index 0000000..c6308a1 --- /dev/null +++ b/examples/dot.clsql-test.config @@ -0,0 +1,14 @@ +;; -*- Mode: Lisp -*- ;; Emacs mode line +;; +;; Example CLSQL test configuration file +;; Since this file is read by Lisp, it is okay to use +;; comments in this file +;; This file should be named .clsql-test.config and +;; placed in your home directory + +((:mysql ("localhost" "a-mysql-db" "user1" "secret")) + (:aodbc ("my-dsn" "a-user" "pass")) + (:postgresql ("localhost" "another-db" "user2" "dont-tell")) + (:postgresql-socket ("pg-server" "a-db-name" "user" "secret-password")) + (:sqlite ("path-to-sqlite-db"))) + diff --git a/examples/run-tests.sh b/examples/run-tests.sh new file mode 100755 index 0000000..acfb767 --- /dev/null +++ b/examples/run-tests.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +REPORT_FILE=/tmp/clsql-test-report.txt +SEXP_REPORT_FILE=/tmp/clsql-test-report.sexp +LISP_CMD_FILE=/tmp/clsql-test.lisp + +CMD=" +(setq *print-readably* nil) +(let ((boot-file + (merge-pathnames + (parse-namestring #+allegro \".clinit.cl\" + #+cmu \".cmucl-init.lisp\" + #+lispworks \".lispworks\" + #+openmcl \"openmcl-init.lisp\" + #+sbcl \".sbclrc\" + #+scl \".scl-init.lisp\" + ) + (user-homedir-pathname)))) + (if (probe-file boot-file) + (load boot-file) + (warn \"Unable to load boot file ~A.\" boot-file))) + + (asdf:operate 'asdf:load-op 'clsql-tests) + (clsql-tests:run-tests-append-report-file \"${REPORT_FILE}\") + + #+allegro (excl:exit) + #+clisp (#+lisp=cl ext:quit #-lisp=cl lisp:quit) + #+cmu (ext:quit) + #+lispworks (lw:quit) + #+openmcl (ccl:quit) + #+sbcl (sb-ext:quit) + #+scl (ext:quit)" + +SUMMARY_CMD=" +(asdf:operate 'asdf:load-op 'clsql-tests) +(clsql-tests:summarize-test-report \"${SEXP_REPORT_FILE}\") + +#+allegro (excl:exit :quiet t) +#+clisp (#+lisp=cl ext:quit #-lisp=cl lisp:quit) +#+cmu (ext:quit) +#+lispworks (lw:quit) +#+openmcl (ccl:quit) +#+sbcl (sb-ext:quit)) +#+scl (ext:quit)" + +rm -rf $REPORT_FILE $SEXP_REPORT_FILE $LISP_CMD_FILE +echo $CMD > $LISP_CMD_FILE + +ALLEGRO=mlisp +if [ "`which $ALLEGRO`" ]; then + $ALLEGRO -q -L $LISP_CMD_FILE +fi + +CMUCL=lisp +if [ "`which $CMUCL`" ]; then + $CMUCL -init $LISP_CMD_FILE +fi + +LISPWORKS=lw-console +#if [ "`which $LISPWORKS`" ]; then + $LISPWORKS -init $LISP_CMD_FILE +#fi + +OPENCML=openmcl +#if [ "`which $OPENMCL`" ]; then + $OPENMCL -init $LISP_CMD_FILE +#fi + +SBCL=sbcl +if [ "`which $SBCL`" ]; then + $SBCL --noinform --disable-debugger --userinit $LISP_CMD_FILE +fi + +if [ -s $SEXP_REPORT_FILE ]; then + echo "$SUMMARY_CMD" | $SBCL +fi + +#rm -rf $LISP_CMD_FILE diff --git a/examples/sqlite3/init-func/Makefile b/examples/sqlite3/init-func/Makefile new file mode 100644 index 0000000..d771bdc --- /dev/null +++ b/examples/sqlite3/init-func/Makefile @@ -0,0 +1,21 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for SQLite 3 init function example. +# Programer: Aurelio Bignoli +# Date Started: Oct 2004 +# +# This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +sqlite3-utils.so: iso-8859-15-coll.c Makefile + gcc -c -fPIC iso-8859-15-coll.c -o iso-8859-15-coll.o + gcc -shared iso-8859-15-coll.o -o sqlite3-utils.so -l sqlite3 + diff --git a/examples/sqlite3/init-func/example.lisp b/examples/sqlite3/init-func/example.lisp new file mode 100644 index 0000000..eb04c07 --- /dev/null +++ b/examples/sqlite3/init-func/example.lisp @@ -0,0 +1,68 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: example.lisp +;;;; Purpose: Sample code for SQLite 3 initialization functions +;;;; Authors: Aurelio Bignoli +;;;; Created: Oct 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +;;;; Load CLSQL. +(asdf:oos 'asdf:load-op :clsql-sqlite3) + +;;;; Load sqlite3-utils.so library. See Makefile for library creation. +(unless (uffi:load-foreign-library "/usr/lib/clsql/sqlite3-utils.so" + :module "sqlite3-utils" + :supporting-libraries '("c")) + (error "Unable to load foreign library")) + +;;;; Define the foreign function to be used as init function. +(uffi:def-function + ("create_iso_8859_15_ci_collation" create-coll) + ((db sqlite3:sqlite3-db)) + :returning :int + :module "sqlite3-utils") + +;;;; Create the DB using create-coll as init function. +(defparameter db-name "init-func-test.db") +(clsql:destroy-database (list db-name) :database-type :sqlite3) +(clsql:connect (list db-name #'create-coll) :database-type :sqlite3) + +;;;; Create a table. Field f2 uses the newly defined collating +;;;; sequence. +(clsql:execute-command + "CREATE TABLE t1 (f1 CHAR(1), f2 CHAR(1) COLLATE ISO_8859_15_CI)") + +;;;; Populate the table. +(clsql:execute-command "INSERT INTO t1 VALUES ('à', 'à')") +(clsql:execute-command "INSERT INTO t1 VALUES ('a', 'a')") +(clsql:execute-command "INSERT INTO t1 VALUES ('A', 'A')") +(clsql:execute-command "INSERT INTO t1 VALUES ('é', 'é')") +(clsql:execute-command "INSERT INTO t1 VALUES ('e', 'e')") +(clsql:execute-command "INSERT INTO t1 VALUES ('E', 'E')") +(clsql:execute-command "INSERT INTO t1 VALUES ('ì', 'ì')") +(clsql:execute-command "INSERT INTO t1 VALUES ('i', 'i')") +(clsql:execute-command "INSERT INTO t1 VALUES ('I', 'I')") +(clsql:execute-command "INSERT INTO t1 VALUES ('ò', 'ò')") +(clsql:execute-command "INSERT INTO t1 VALUES ('o', 'o')") +(clsql:execute-command "INSERT INTO t1 VALUES ('O', 'O')") +(clsql:execute-command "INSERT INTO t1 VALUES ('ù', 'ù')") +(clsql:execute-command "INSERT INTO t1 VALUES ('u', 'u')") +(clsql:execute-command "INSERT INTO t1 VALUES ('U', 'U')") + +;;;; Perform some SELECTs. +(format t "~&SELECT * FROM t1 ==> ~A~%"(clsql:query "SELECT * FROM t1")) +(format t "~&SELECT * FROM t1 ORDER BY f1 ==> ~A~%" + (clsql:query "SELECT * FROM t1 ORDER BY f1")) +(format t "~&SELECT * FROM t1 ORDER BY f2 ==> ~A~%" + (clsql:query "SELECT * FROM t1 ORDER BY f2")) + +;;;; Disconnect from database. +(clsql:disconnect) diff --git a/examples/sqlite3/init-func/iso-8859-15-coll.c b/examples/sqlite3/init-func/iso-8859-15-coll.c new file mode 100644 index 0000000..a5d0f8e --- /dev/null +++ b/examples/sqlite3/init-func/iso-8859-15-coll.c @@ -0,0 +1,77 @@ +/**************************************************************************** + * FILE IDENTIFICATION + * + * Name: iso-8859-15-coll.c + * Purpose: SQLite 3 initialization function for + * ISO-8859-15 collating sequence. + * Programmer: Aurelio Bignoli + * Date Started: Oct 2004 + * + * This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli + * + * CLSQL users are granted the rights to distribute and use this software + * as governed by the terms of the Lisp Lesser GNU Public License + * (http://opensource.franz.com/preamble.html), also known as the LLGPL. + ***************************************************************************/ + + +/* Collating sequence name. CI = Case Insensitive */ +#define ISO_8859_15_CI_NAME "ISO_8859_15_CI" + +/* Conversion table. */ +const unsigned char iso_8859_15_ci [] = { + /* 0 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + /* 1 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + /* 2 */ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + /* 3 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + /* 4 */ 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + /* 5 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + /* 6 */ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + /* 7 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + /* 8 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x73, 0x8B, 0x6F, 0x8D, 0x7A, 0x79, + /* 9 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x73, 0x9B, 0x6F, 0x9D, 0x7A, 0x79, + /* A */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x73, 0xA7, 0x73, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + /* B */ 0xB0, 0xB1, 0xB2, 0xB3, 0x7A, 0xB5, 0xB6, 0xB7, 0x7A, 0xB9, 0xBA, 0xBB, 0x6F, 0xBD, 0x79, 0xBF, + /* C */ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x65, 0x63, 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69, + /* D */ 0x64, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0xD7, 0x6F, 0x75, 0x75, 0x75, 0x75, 0x79, 0xDE, 0x73, + /* E */ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x65, 0x63, 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69, + /* F */ 0x64, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0xF7, 0x6F, 0x75, 0x75, 0x75, 0x75, 0x79, 0xFE, 0x73 +}; + +/* + * A modified version of sqlite3StrNICmp in sqlite/src/util.c + */ +int iso_8859_15_ci_StrCmp(const char *zLeft, const char *zRight, int N){ + register unsigned char *a, *b; + a = (unsigned char *)zLeft; + b = (unsigned char *)zRight; + while( N-- > 0 && *a!=0 && iso_8859_15_ci[*a]==iso_8859_15_ci[*b]){ a++; b++; } + return N<0 ? 0 : iso_8859_15_ci[*a] - iso_8859_15_ci[*b]; +} + +/* + * A modified version of nocaseCollatinFunc in sqlite/src/main.c. + */ +int iso_8859_15_ci_CollatingFunc( + void *NotUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + int r = iso_8859_15_ci_StrCmp( + (const char *)pKey1, (const char *)pKey2, (nKey1/-objects.lisp or +sql/generic-.lisp. diff --git a/sql/Makefile b/sql/Makefile new file mode 100644 index 0000000..1f71fb2 --- /dev/null +++ b/sql/Makefile @@ -0,0 +1,24 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL SQL interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean + diff --git a/sql/ansi-loop.lisp b/sql/ansi-loop.lisp new file mode 100644 index 0000000..6a2cab4 --- /dev/null +++ b/sql/ansi-loop.lisp @@ -0,0 +1,2282 @@ +;;; -*- Mode: LISP; Package: ANSI-LOOP; Syntax: Common-lisp; Base: 10; Lowercase:T -*- +;;; +;;; This file is included with CLSQL to be used by CLISP which does not +;;; have an extensible LOOP macro. It was copied from the CMUCL 19c source. +;;; Minor porting changes have been made Copyright (c) 2006 Kevin M. Rosenberg +;;; +;;;> +;;;> Portions of LOOP are Copyright (c) 1986 by the Massachusetts Institute of Technology. +;;;> All Rights Reserved. +;;;> +;;;> Permission to use, copy, modify and distribute this software and its +;;;> documentation for any purpose and without fee is hereby granted, +;;;> provided that the M.I.T. copyright notice appear in all copies and that +;;;> both that copyright notice and this permission notice appear in +;;;> supporting documentation. The names "M.I.T." and "Massachusetts +;;;> Institute of Technology" may not be used in advertising or publicity +;;;> pertaining to distribution of the software without specific, written +;;;> prior permission. Notice must be given in supporting documentation that +;;;> copying distribution is by permission of M.I.T. M.I.T. makes no +;;;> representations about the suitability of this software for any purpose. +;;;> It is provided "as is" without express or implied warranty. +;;;> +;;;> Massachusetts Institute of Technology +;;;> 77 Massachusetts Avenue +;;;> Cambridge, Massachusetts 02139 +;;;> United States of America +;;;> +1-617-253-1000 +;;;> +;;;> Portions of LOOP are Copyright (c) 1989, 1990, 1991, 1992 by Symbolics, Inc. +;;;> All Rights Reserved. +;;;> +;;;> Permission to use, copy, modify and distribute this software and its +;;;> documentation for any purpose and without fee is hereby granted, +;;;> provided that the Symbolics copyright notice appear in all copies and +;;;> that both that copyright notice and this permission notice appear in +;;;> supporting documentation. The name "Symbolics" may not be used in +;;;> advertising or publicity pertaining to distribution of the software +;;;> without specific, written prior permission. Notice must be given in +;;;> supporting documentation that copying distribution is by permission of +;;;> Symbolics. Symbolics makes no representations about the suitability of +;;;> this software for any purpose. It is provided "as is" without express +;;;> or implied warranty. +;;;> +;;;> Symbolics, CLOE Runtime, and Minima are trademarks, and CLOE, Genera, +;;;> and Zetalisp are registered trademarks of Symbolics, Inc. +;;;> +;;;> Symbolics, Inc. +;;;> 8 New England Executive Park, East +;;;> Burlington, Massachusetts 01803 +;;;> United States of America +;;;> +1-617-221-1000 + +;; $aclHeader: loop.cl,v 1.5 91/12/04 01:13:48 cox acl4_1 $ +#+cmu +(ext:file-comment + "$Header: /project/cmucl/cvsroot/src/code/loop.lisp,v 1.27 2004/10/21 02:31:08 rtoy Exp $") + + +;;;; LOOP Iteration Macro + +#+clisp +(eval-when (:compile-toplevel :load-toplevel :execute) + (setf (ext:package-lock (find-package "COMMON-LISP")) nil)) +(defpackage ansi-loop (:use :common-lisp) + (:shadowing-import-from "COMMON-LISP" "LOOP" "LOOP-FINISH")) +(in-package ansi-loop) + +;;; Technology. +;;; +;;; The LOOP iteration macro is one of a number of pieces of code +;;; originally developed at MIT and licensed as set out above. This +;;; version of LOOP, which is almost entirely rewritten both as a +;;; clean-up and to conform with the ANSI Lisp LOOP standard, started +;;; life as MIT LOOP version 829 (which was a part of NIL, possibly +;;; never released). +;;; +;;; A "light revision" was performed by Glenn Burke while at Palladian +;;; Software in April 1986, to make the code run in Common Lisp. This +;;; revision was informally distributed to a number of people, and was +;;; sort of the "MIT" version of LOOP for running in Common Lisp. +;;; +;;; A later more drastic revision was performed at Palladian perhaps a +;;; year later. This version was more thoroughly Common Lisp in +;;; style, with a few miscellaneous internal improvements and +;;; extensions. Glenn Burke lost track of this source, apparently +;;; never having moved it to the MIT distribution point; and does not +;;; remember if it was ever distributed. +;;; +;;; This revision for the ANSI standard is based on the code of Glenn +;;; Burke's April 1986 version, with almost everything redesigned +;;; and/or rewritten. + + +;;; The design of this LOOP is intended to permit, using mostly the same +;;; kernel of code, up to three different "loop" macros: +;;; +;;; (1) The unextended, unextensible ANSI standard LOOP; +;;; +;;; (2) A clean "superset" extension of the ANSI LOOP which provides +;;; functionality similar to that of the old LOOP, but "in the style of" +;;; the ANSI LOOP. For instance, user-definable iteration paths, with a +;;; somewhat cleaned-up interface. +;;; +;;; (3) Extensions provided in another file which can make this LOOP +;;; kernel behave largely compatibly with the Genera-vintage LOOP macro, +;;; with only a small addition of code (instead of two whole, separate, +;;; LOOP macros). +;;; +;;; Each of the above three LOOP variations can coexist in the same LISP +;;; environment. +;;; + + +;;;; Miscellaneous Environment Things + + + +;;;@@@@The LOOP-Prefer-POP feature makes LOOP generate code which "prefers" to use POP or +;;; its obvious expansion (prog1 (car x) (setq x (cdr x))). Usually this involves +;;; shifting fenceposts in an iteration or series of carcdr operations. This is +;;; primarily recognized in the list iterators (FOR .. {IN,ON}), and LOOP's +;;; destructuring setq code. +(eval-when (compile load eval) + #+(or Genera Minima) (pushnew :LOOP-Prefer-POP *features*) + ) + + +;;; The uses of this macro are retained in the CL version of loop, in +;;; case they are needed in a particular implementation. Originally +;;; dating from the use of the Zetalisp COPYLIST* function, this is used +;;; in situations where, were cdr-coding in use, having cdr-NIL at the +;;; end of the list might be suboptimal because the end of the list will +;;; probably be RPLACDed and so cdr-normal should be used instead. +(defmacro loop-copylist* (l) + #+Genera `(lisp:copy-list ,l nil t) ; arglist = (list &optional area force-dotted) + ;;@@@@Explorer?? + #-Genera `(copy-list ,l) + ) + + +(defvar *loop-gentemp* t) + +(defun loop-gentemp (&optional (pref 'loopvar-)) + (if *loop-gentemp* + (gensym (string pref)) + (gensym))) + + + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defvar *loop-real-data-type* 'real)) + + +(defun loop-optimization-quantities (env) + ;;@@@@ The ANSI conditionalization here is for those lisps that implement + ;; DECLARATION-INFORMATION (from cleanup SYNTACTIC-ENVIRONMENT-ACCESS). + ;; It is really commentary on how this code could be written. I don't + ;; actually expect there to be an ANSI #+-conditional -- it should be + ;; replaced with the appropriate conditional name for your + ;; implementation/dialect. + (declare #-ANSI (ignore env) + #+Genera (values speed space safety compilation-speed debug)) + #+ANSI (let ((stuff (declaration-information 'optimize env))) + (values (or (cdr (assoc 'speed stuff)) 1) + (or (cdr (assoc 'space stuff)) 1) + (or (cdr (assoc 'safety stuff)) 1) + (or (cdr (assoc 'compilation-speed stuff)) 1) + (or (cdr (assoc 'debug stuff)) 1))) + #+CLOE-Runtime (values compiler::time compiler::space + compiler::safety compiler::compilation-speed 1) + #-(or ANSI CLOE-Runtime) (values 1 1 1 1 1)) + + +;;;@@@@ The following form takes a list of variables and a form which presumably +;;; references those variables, and wraps it somehow so that the compiler does not +;;; consider those variables have been referenced. The intent of this is that +;;; iteration variables can be flagged as unused by the compiler, e.g. I in +;;; (loop for i from 1 to 10 do (print t)), since we will tell it when a usage +;;; of it is "invisible" or "not to be considered". +;;;We implicitly assume that a setq does not count as a reference. That is, the +;;; kind of form generated for the above loop construct to step I, simplified, is +;;; `(SETQ I ,(HIDE-VARIABLE-REFERENCES '(I) '(1+ I))). +(defun hide-variable-references (variable-list form) + (declare #-Genera (ignore variable-list)) + #+Genera (if variable-list `(compiler:invisible-references ,variable-list ,form) form) + #-Genera form) + + +;;;@@@@ The following function takes a flag, a variable, and a form which presumably +;;; references that variable, and wraps it somehow so that the compiler does not +;;; consider that variable to have been referenced. The intent of this is that +;;; iteration variables can be flagged as unused by the compiler, e.g. I in +;;; (loop for i from 1 to 10 do (print t)), since we will tell it when a usage +;;; of it is "invisible" or "not to be considered". +;;;We implicitly assume that a setq does not count as a reference. That is, the +;;; kind of form generated for the above loop construct to step I, simplified, is +;;; `(SETQ I ,(HIDE-VARIABLE-REFERENCES T 'I '(1+ I))). +;;;Certain cases require that the "invisibility" of the reference be conditional upon +;;; something. This occurs in cases of "named" variables (the USING clause). For instance, +;;; we want IDX in (LOOP FOR E BEING THE VECTOR-ELEMENTS OF V USING (INDEX IDX) ...) +;;; to be "invisible" when it is stepped, so that the user gets informed if IDX is +;;; not referenced. However, if no USING clause is present, we definitely do not +;;; want to be informed that some random gensym is not used. +;;;It is easier for the caller to do this conditionally by passing a flag (which +;;; happens to be the second value of NAMED-VARIABLE, q.v.) to this function than +;;; for all callers to contain the conditional invisibility construction. +(defun hide-variable-reference (really-hide variable form) + (declare #-Genera (ignore really-hide variable)) + #+Genera (if (and really-hide variable (atom variable)) ;Punt on destructuring patterns + `(compiler:invisible-references (,variable) ,form) + form) + #-Genera form) + + +;;;; List Collection Macrology + + +(defmacro with-loop-list-collection-head ((head-var tail-var &optional user-head-var) + &body body) + ;;@@@@ TI? Exploder? + #+LISPM (let ((head-place (or user-head-var head-var))) + `(let* ((,head-place nil) + (,tail-var + ,(hide-variable-reference + user-head-var user-head-var + `(progn #+Genera (scl:locf ,head-place) + #-Genera (system:variable-location ,head-place))))) + ,@body)) + #-LISPM (let ((l (and user-head-var (list (list user-head-var nil))))) + #+CLOE `(sys::with-stack-list* (,head-var nil nil) + (let ((,tail-var ,head-var) ,@l) + ,@body)) + #-CLOE `(let* ((,head-var (list nil)) (,tail-var ,head-var) ,@l) + ,@body))) + + +(defmacro loop-collect-rplacd (&environment env + (head-var tail-var &optional user-head-var) form) + (declare + #+LISPM (ignore head-var user-head-var) ;use locatives, unconditionally update through the tail. + ) + (setq form (macroexpand form env)) + (flet ((cdr-wrap (form n) + (declare (fixnum n)) + (do () ((<= n 4) (setq form `(,(case n + (1 'cdr) + (2 'cddr) + (3 'cdddr) + (4 'cddddr)) + ,form))) + (setq form `(cddddr ,form) n (- n 4))))) + (let ((tail-form form) (ncdrs nil)) + ;;Determine if the form being constructed is a list of known length. + (when (consp form) + (cond ((eq (car form) 'list) + (setq ncdrs (1- (length (cdr form)))) + ;;@@@@ Because the last element is going to be RPLACDed, + ;; we don't want the cdr-coded implementations to use + ;; cdr-nil at the end (which would just force copying + ;; the whole list again). + #+LISPM (setq tail-form `(list* ,@(cdr form) nil))) + ((member (car form) '(list* cons)) + (when (and (cddr form) (member (car (last form)) '(nil 'nil))) + (setq ncdrs (- (length (cdr form)) 2)))))) + (let ((answer + (cond ((null ncdrs) + `(when (setf (cdr ,tail-var) ,tail-form) + (setq ,tail-var (last (cdr ,tail-var))))) + ((< ncdrs 0) (return-from loop-collect-rplacd nil)) + ((= ncdrs 0) + ;;@@@@ Here we have a choice of two idioms: + ;; (rplacd tail (setq tail tail-form)) + ;; (setq tail (setf (cdr tail) tail-form)). + ;;Genera and most others I have seen do better with the former. + `(rplacd ,tail-var (setq ,tail-var ,tail-form))) + (t `(setq ,tail-var ,(cdr-wrap `(setf (cdr ,tail-var) ,tail-form) + ncdrs)))))) + ;;If not using locatives or something similar to update the user's + ;; head variable, we've got to set it... It's harmless to repeatedly set it + ;; unconditionally, and probably faster than checking. + #-LISPM (when user-head-var + (setq answer `(progn ,answer (setq ,user-head-var (cdr ,head-var))))) + answer)))) + + +(defmacro loop-collect-answer (head-var &optional user-head-var) + (or user-head-var + (progn + ;;If we use locatives to get tail-updating to update the head var, + ;; then the head var itself contains the answer. Otherwise we + ;; have to cdr it. + #+LISPM head-var + #-LISPM `(cdr ,head-var)))) + + +;;;; Maximization Technology + + +#| +The basic idea of all this minimax randomness here is that we have to +have constructed all uses of maximize and minimize to a particular +"destination" before we can decide how to code them. The goal is to not +have to have any kinds of flags, by knowing both that (1) the type is +something which we can provide an initial minimum or maximum value for +and (2) know that a MAXIMIZE and MINIMIZE are not being combined. + +SO, we have a datastructure which we annotate with all sorts of things, +incrementally updating it as we generate loop body code, and then use +a wrapper and internal macros to do the coding when the loop has been +constructed. +|# + + +(defstruct (loop-minimax + (:constructor make-loop-minimax-internal) + (:copier nil) + (:predicate nil)) + answer-variable + type + temp-variable + flag-variable + operations + infinity-data) + + +(defvar *loop-minimax-type-infinities-alist* + ;;@@@@ This is the sort of value this should take on for a Lisp that has + ;; "eminently usable" infinities. n.b. there are neither constants nor + ;; printed representations for infinities defined by CL. + ;;@@@@ This grotesque read-from-string below is to help implementations + ;; which croak on the infinity character when it appears in a token, even + ;; conditionalized out. + #+Genera + '#.(read-from-string + "((fixnum most-positive-fixnum most-negative-fixnum) + (short-float +1s -1s) + (single-float +1f -1f) + (double-float +1d -1d) + (long-float +1l -1l))") + ;;This is how the alist should look for a lisp that has no infinities. In + ;; that case, MOST-POSITIVE-x-FLOAT really IS the most positive. + #+(or CLOE-Runtime Minima) + '((fixnum most-positive-fixnum most-negative-fixnum) + (short-float most-positive-short-float most-negative-short-float) + (single-float most-positive-single-float most-negative-single-float) + (double-float most-positive-double-float most-negative-double-float) + (long-float most-positive-long-float most-negative-long-float)) + ;; CMUCL has infinities so let's use them. + #+CMU + '((fixnum most-positive-fixnum most-negative-fixnum) + (short-float ext:single-float-positive-infinity ext:single-float-negative-infinity) + (single-float ext:single-float-positive-infinity ext:single-float-negative-infinity) + (double-float ext:double-float-positive-infinity ext:double-float-negative-infinity) + (long-float ext:long-float-positive-infinity ext:long-float-negative-infinity)) + ;; If we don't know, then we cannot provide "infinite" initial values for any of the + ;; types but FIXNUM: + #-(or Genera CLOE-Runtime Minima CMU) + '((fixnum most-positive-fixnum most-negative-fixnum)) + ) + + +(defun make-loop-minimax (answer-variable type) + (let ((infinity-data (cdr (assoc type *loop-minimax-type-infinities-alist* :test #'subtypep)))) + (make-loop-minimax-internal + :answer-variable answer-variable + :type type + :temp-variable (loop-gentemp 'loop-maxmin-temp-) + :flag-variable (and (not infinity-data) (loop-gentemp 'loop-maxmin-flag-)) + :operations nil + :infinity-data infinity-data))) + + +(defun loop-note-minimax-operation (operation minimax) + (pushnew (the symbol operation) (loop-minimax-operations minimax)) + (when (and (cdr (loop-minimax-operations minimax)) + (not (loop-minimax-flag-variable minimax))) + (setf (loop-minimax-flag-variable minimax) (loop-gentemp 'loop-maxmin-flag-))) + operation) + + +(defmacro with-minimax-value (lm &body body) + (let ((init (loop-typed-init (loop-minimax-type lm))) + (which (car (loop-minimax-operations lm))) + (infinity-data (loop-minimax-infinity-data lm)) + (answer-var (loop-minimax-answer-variable lm)) + (temp-var (loop-minimax-temp-variable lm)) + (flag-var (loop-minimax-flag-variable lm)) + (type (loop-minimax-type lm))) + (if flag-var + `(let ((,answer-var ,init) (,temp-var ,init) (,flag-var nil)) + (declare (type ,type ,answer-var ,temp-var)) + ,@body) + `(let ((,answer-var ,(if (eq which 'min) (first infinity-data) (second infinity-data))) + (,temp-var ,init)) + (declare (type ,type ,answer-var ,temp-var)) + ,@body)))) + + +(defmacro loop-accumulate-minimax-value (lm operation form) + (let* ((answer-var (loop-minimax-answer-variable lm)) + (temp-var (loop-minimax-temp-variable lm)) + (flag-var (loop-minimax-flag-variable lm)) + (test + (hide-variable-reference + t (loop-minimax-answer-variable lm) + `(,(ecase operation + (min '<) + (max '>)) + ,temp-var ,answer-var)))) + `(progn + (setq ,temp-var ,form) + (when ,(if flag-var `(or (not ,flag-var) ,test) test) + (setq ,@(and flag-var `(,flag-var t)) + ,answer-var ,temp-var))))) + + + +;;;; Loop Keyword Tables + + +#| +LOOP keyword tables are hash tables string keys and a test of EQUAL. + +The actual descriptive/dispatch structure used by LOOP is called a "loop +universe" contains a few tables and parameterizations. The basic idea is +that we can provide a non-extensible ANSI-compatible loop environment, +an extensible ANSI-superset loop environment, and (for such environments +as CLOE) one which is "sufficiently close" to the old Genera-vintage +LOOP for use by old user programs without requiring all of the old LOOP +code to be loaded. +|# + + +;;;; Token Hackery + + +;;;Compare two "tokens". The first is the frob out of *LOOP-SOURCE-CODE*, +;;; the second a symbol to check against. +(defun loop-tequal (x1 x2) + (and (symbolp x1) (string= x1 x2))) + + +(defun loop-tassoc (kwd alist) + (and (symbolp kwd) (assoc kwd alist :test #'string=))) + + +(defun loop-tmember (kwd list) + (and (symbolp kwd) (member kwd list :test #'string=))) + + +(defun loop-lookup-keyword (loop-token table) + (and (symbolp loop-token) + (values (gethash (symbol-name loop-token) table)))) + + +(defmacro loop-store-table-data (symbol table datum) + `(setf (gethash (symbol-name ,symbol) ,table) ,datum)) + + +(defstruct (loop-universe + (:print-function print-loop-universe) + (:copier nil) + (:predicate nil)) + keywords ;hash table, value = (fn-name . extra-data). + iteration-keywords ;hash table, value = (fn-name . extra-data). + for-keywords ;hash table, value = (fn-name . extra-data). + path-keywords ;hash table, value = (fn-name . extra-data). + type-symbols ;hash table of type SYMBOLS, test EQ, value = CL type specifier. + type-keywords ;hash table of type STRINGS, test EQUAL, value = CL type spec. + ansi ;NIL, T, or :EXTENDED. + implicit-for-required ;see loop-hack-iteration + ) + + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defun print-loop-universe (u stream level) + (declare (ignore level)) + (let ((str (case (loop-universe-ansi u) + ((nil) "Non-ANSI") + ((t) "ANSI") + (:extended "Extended-ANSI") + (t (loop-universe-ansi u))))) + ;;Cloe could be done with the above except for bootstrap lossage... + #+CLOE + (format stream "#<~S ~A ~X>" (type-of u) str (sys::address-of u)) + (print-unreadable-object (u stream :type t :identity t) + (princ str stream)) + ))) + + +;;;This is the "current" loop context in use when we are expanding a +;;;loop. It gets bound on each invocation of LOOP. +(defvar *loop-universe*) + + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defun make-standard-loop-universe (&key keywords for-keywords iteration-keywords path-keywords + type-keywords type-symbols ansi) + #-(and CLOE Source-Bootstrap) (check-type ansi (member nil t :extended)) + (flet ((maketable (entries) + (let* ((size (length entries)) + (ht (make-hash-table :size (if (< size 10) 10 size) :test #'equal))) + (dolist (x entries) (setf (gethash (symbol-name (car x)) ht) (cadr x))) + ht))) + (make-loop-universe + :keywords (maketable keywords) + :for-keywords (maketable for-keywords) + :iteration-keywords (maketable iteration-keywords) + :path-keywords (maketable path-keywords) + :ansi ansi + :implicit-for-required (not (null ansi)) + :type-keywords (maketable type-keywords) + :type-symbols (let* ((size (length type-symbols)) + (ht (make-hash-table :size (if (< size 10) 10 size) :test #'eq))) + (dolist (x type-symbols) + (if (atom x) (setf (gethash x ht) x) (setf (gethash (car x) ht) (cadr x)))) + ht))))) + + + +;;;; Setq Hackery + + +(defvar *loop-destructuring-hooks* + nil + "If not NIL, this must be a list of two things: +a LET-like macro, and a SETQ-like macro, which perform LOOP-style destructuring.") + + +(defun loop-make-psetq (frobs) + (and frobs + (loop-make-desetq + (list (car frobs) + (if (null (cddr frobs)) (cadr frobs) + `(prog1 ,(cadr frobs) + ,(loop-make-psetq (cddr frobs)))))))) + + +(defun loop-make-desetq (var-val-pairs) + (if (null var-val-pairs) + nil + (cons (if *loop-destructuring-hooks* + (cadr *loop-destructuring-hooks*) + 'loop-really-desetq) + var-val-pairs))) + + +(defvar *loop-desetq-temporary* + (make-symbol "LOOP-DESETQ-TEMP")) + + +(defmacro loop-really-desetq (&environment env &rest var-val-pairs) + (labels ((find-non-null (var) + ;; see if there's any non-null thing here + ;; recurse if the list element is itself a list + (do ((tail var)) ((not (consp tail)) tail) + (when (find-non-null (pop tail)) (return t)))) + (loop-desetq-internal (var val &optional temp) + ;; returns a list of actions to be performed + (typecase var + (null + (when (consp val) + ;; don't lose possible side-effects + (if (eq (car val) 'prog1) + ;; these can come from psetq or desetq below. + ;; throw away the value, keep the side-effects. + ;;Special case is for handling an expanded POP. + (mapcan #'(lambda (x) + (and (consp x) + (or (not (eq (car x) 'car)) + (not (symbolp (cadr x))) + (not (symbolp (setq x (macroexpand x env))))) + (cons x nil))) + (cdr val)) + `(,val)))) + (cons + (let* ((car (car var)) + (cdr (cdr var)) + (car-non-null (find-non-null car)) + (cdr-non-null (find-non-null cdr))) + (when (or car-non-null cdr-non-null) + (if cdr-non-null + (let* ((temp-p temp) + (temp (or temp *loop-desetq-temporary*)) + (body #+LOOP-Prefer-POP `(,@(loop-desetq-internal + car + `(prog1 (car ,temp) + (setq ,temp (cdr ,temp)))) + ,@(loop-desetq-internal cdr temp temp)) + #-LOOP-Prefer-POP `(,@(loop-desetq-internal car `(car ,temp)) + (setq ,temp (cdr ,temp)) + ,@(loop-desetq-internal cdr temp temp)))) + (if temp-p + `(,@(unless (eq temp val) + `((setq ,temp ,val))) + ,@body) + `((let ((,temp ,val)) + ,@body)))) + ;; no cdring to do + (loop-desetq-internal car `(car ,val) temp))))) + (otherwise + (unless (eq var val) + `((setq ,var ,val))))))) + (do ((actions)) + ((null var-val-pairs) + (if (null (cdr actions)) (car actions) `(progn ,@(nreverse actions)))) + (setq actions (revappend + (loop-desetq-internal (pop var-val-pairs) (pop var-val-pairs)) + actions))))) + + +;;;; LOOP-local variables + +;;;This is the "current" pointer into the LOOP source code. +(defvar *loop-source-code*) + + +;;;This is the pointer to the original, for things like NAMED that +;;;insist on being in a particular position +(defvar *loop-original-source-code*) + + +;;;This is *loop-source-code* as of the "last" clause. It is used +;;;primarily for generating error messages (see loop-error, loop-warn). +(defvar *loop-source-context*) + + +;;;List of names for the LOOP, supplied by the NAMED clause. +(defvar *loop-names*) + +;;;The macroexpansion environment given to the macro. +(defvar *loop-macro-environment*) + +;;;This holds variable names specified with the USING clause. +;;; See LOOP-NAMED-VARIABLE. +(defvar *loop-named-variables*) + +;;; LETlist-like list being accumulated for one group of parallel bindings. +(defvar *loop-variables*) + +;;;List of declarations being accumulated in parallel with +;;;*loop-variables*. +(defvar *loop-declarations*) + +;;;Used by LOOP for destructuring binding, if it is doing that itself. +;;; See loop-make-variable. +(defvar *loop-desetq-crocks*) + +;;; List of wrapping forms, innermost first, which go immediately inside +;;; the current set of parallel bindings being accumulated in +;;; *loop-variables*. The wrappers are appended onto a body. E.g., +;;; this list could conceivably has as its value ((with-open-file (g0001 +;;; g0002 ...))), with g0002 being one of the bindings in +;;; *loop-variables* (this is why the wrappers go inside of the variable +;;; bindings). +(defvar *loop-wrappers*) + +;;;This accumulates lists of previous values of *loop-variables* and the +;;;other lists above, for each new nesting of bindings. See +;;;loop-bind-block. +(defvar *loop-bind-stack*) + +;;;This is a LOOP-global variable for the (obsolete) NODECLARE clause +;;;which inhibits LOOP from actually outputting a type declaration for +;;;an iteration (or any) variable. +(defvar *loop-nodeclare*) + +;;;This is simply a list of LOOP iteration variables, used for checking +;;;for duplications. +(defvar *loop-iteration-variables*) + + +;;;List of prologue forms of the loop, accumulated in reverse order. +(defvar *loop-prologue*) + +(defvar *loop-before-loop*) +(defvar *loop-body*) +(defvar *loop-after-body*) + +;;;This is T if we have emitted any body code, so that iteration driving +;;;clauses can be disallowed. This is not strictly the same as +;;;checking *loop-body*, because we permit some clauses such as RETURN +;;;to not be considered "real" body (so as to permit the user to "code" +;;;an abnormal return value "in loop"). +(defvar *loop-emitted-body*) + + +;;;List of epilogue forms (supplied by FINALLY generally), accumulated +;;; in reverse order. +(defvar *loop-epilogue*) + +;;;List of epilogue forms which are supplied after the above "user" +;;;epilogue. "normal" termination return values are provide by putting +;;;the return form in here. Normally this is done using +;;;loop-emit-final-value, q.v. +(defvar *loop-after-epilogue*) + +;;;The "culprit" responsible for supplying a final value from the loop. +;;;This is so loop-emit-final-value can moan about multiple return +;;;values being supplied. +(defvar *loop-final-value-culprit*) + +;;;If not NIL, we are in some branch of a conditional. Some clauses may +;;;be disallowed. +(defvar *loop-inside-conditional*) + +;;;If not NIL, this is a temporary bound around the loop for holding the +;;;temporary value for "it" in things like "when (f) collect it". It +;;;may be used as a supertemporary by some other things. +(defvar *loop-when-it-variable*) + +;;;Sometimes we decide we need to fold together parts of the loop, but +;;;some part of the generated iteration code is different for the first +;;;and remaining iterations. This variable will be the temporary which +;;;is the flag used in the loop to tell whether we are in the first or +;;;remaining iterations. +(defvar *loop-never-stepped-variable*) + +;;;List of all the value-accumulation descriptor structures in the loop. +;;; See loop-get-collection-info. +(defvar *loop-collection-cruft*) ; for multiple COLLECTs (etc) + + +;;;; Code Analysis Stuff + + +(defun loop-constant-fold-if-possible (form &optional expected-type) + #+Genera (declare (values new-form constantp constant-value)) + (let ((new-form form) (constantp nil) (constant-value nil)) + #+Genera (setq new-form (compiler:optimize-form form *loop-macro-environment* + :repeat t + :do-macro-expansion t + :do-named-constants t + :do-inline-forms t + :do-optimizers t + :do-constant-folding t + :do-function-args t) + constantp (constantp new-form *loop-macro-environment*) + constant-value (and constantp (lt:evaluate-constant new-form *loop-macro-environment*))) + #-Genera (when (setq constantp (constantp new-form)) + (setq constant-value (eval new-form))) + (when (and constantp expected-type) + (unless (typep constant-value expected-type) + (loop-warn "The form ~S evaluated to ~S, which was not of the anticipated type ~S." + form constant-value expected-type) + (setq constantp nil constant-value nil))) + (values new-form constantp constant-value))) + + +(defun loop-constantp (form) + #+Genera (constantp form *loop-macro-environment*) + #-Genera (constantp form)) + + +;;;; LOOP Iteration Optimization + +(defvar *loop-duplicate-code* + nil) + + +(defvar *loop-iteration-flag-variable* + (make-symbol "LOOP-NOT-FIRST-TIME")) + + +(defun loop-code-duplication-threshold (env) + (multiple-value-bind (speed space) (loop-optimization-quantities env) + (+ 40 (* (- speed space) 10)))) + + +(defmacro loop-body (&environment env + prologue + before-loop + main-body + after-loop + epilogue + &aux rbefore rafter flagvar) + (unless (= (length before-loop) (length after-loop)) + (error "LOOP-BODY called with non-synched before- and after-loop lists.")) + ;;All our work is done from these copies, working backwards from the end: + (setq rbefore (reverse before-loop) rafter (reverse after-loop)) + (labels ((psimp (l) + (let ((ans nil)) + (dolist (x l) + (when x + (push x ans) + (when (and (consp x) (member (car x) '(go return return-from))) + (return nil)))) + (nreverse ans))) + (pify (l) (if (null (cdr l)) (car l) `(progn ,@l))) + (makebody () + (let ((form `(tagbody + ;; ANSI CL 6.1.7.2 says that initially clauses are + ;; evaluated in the loop prologue, which precedes + ;; all loop code except for the initial settings + ;; provided by with, for, or as. + ,@(psimp (append (nreverse rbefore) prologue)) + next-loop + ,@(psimp (append main-body (nreconc rafter `((go next-loop))))) + end-loop + ,@(psimp epilogue)))) + (if flagvar `(let ((,flagvar nil)) ,form) form)))) + (when (or *loop-duplicate-code* (not rbefore)) + (return-from loop-body (makebody))) + ;; This outer loop iterates once for each not-first-time flag test generated + ;; plus once more for the forms that don't need a flag test + (do ((threshold (loop-code-duplication-threshold env))) (nil) + (declare (fixnum threshold)) + ;; Go backwards from the ends of before-loop and after-loop merging all the equivalent + ;; forms into the body. + (do () ((or (null rbefore) (not (equal (car rbefore) (car rafter))))) + (push (pop rbefore) main-body) + (pop rafter)) + (unless rbefore (return (makebody))) + ;; The first forms in rbefore & rafter (which are the chronologically + ;; last forms in the list) differ, therefore they cannot be moved + ;; into the main body. If everything that chronologically precedes + ;; them either differs or is equal but is okay to duplicate, we can + ;; just put all of rbefore in the prologue and all of rafter after + ;; the body. Otherwise, there is something that is not okay to + ;; duplicate, so it and everything chronologically after it in + ;; rbefore and rafter must go into the body, with a flag test to + ;; distinguish the first time around the loop from later times. + ;; What chronologically precedes the non-duplicatable form will + ;; be handled the next time around the outer loop. + (do ((bb rbefore (cdr bb)) (aa rafter (cdr aa)) (lastdiff nil) (count 0) (inc nil)) + ((null bb) (return-from loop-body (makebody))) ;Did it. + (cond ((not (equal (car bb) (car aa))) (setq lastdiff bb count 0)) + ((or (not (setq inc (estimate-code-size (car bb) env))) + (> (incf count inc) threshold)) + ;; Ok, we have found a non-duplicatable piece of code. Everything + ;; chronologically after it must be in the central body. + ;; Everything chronologically at and after lastdiff goes into the + ;; central body under a flag test. + (let ((then nil) (else nil)) + (do () (nil) + (push (pop rbefore) else) + (push (pop rafter) then) + (when (eq rbefore (cdr lastdiff)) (return))) + (unless flagvar + (push `(setq ,(setq flagvar *loop-iteration-flag-variable*) t) else)) + (push `(if ,flagvar ,(pify (psimp then)) ,(pify (psimp else))) + main-body)) + ;; Everything chronologically before lastdiff until the non-duplicatable form (car bb) + ;; is the same in rbefore and rafter so just copy it into the body + (do () (nil) + (pop rafter) + (push (pop rbefore) main-body) + (when (eq rbefore (cdr bb)) (return))) + (return))))))) + + + +(defun duplicatable-code-p (expr env) + (if (null expr) 0 + (let ((ans (estimate-code-size expr env))) + (declare (fixnum ans)) + ;;@@@@ Use (DECLARATION-INFORMATION 'OPTIMIZE ENV) here to get an alist of + ;; optimize quantities back to help quantify how much code we are willing to + ;; duplicate. + ans))) + + +(defvar *special-code-sizes* + '((return 0) (progn 0) + (null 1) (not 1) (eq 1) (car 1) (cdr 1) + (when 1) (unless 1) (if 1) + (caar 2) (cadr 2) (cdar 2) (cddr 2) + (caaar 3) (caadr 3) (cadar 3) (caddr 3) (cdaar 3) (cdadr 3) (cddar 3) (cdddr 3) + (caaaar 4) (caaadr 4) (caadar 4) (caaddr 4) + (cadaar 4) (cadadr 4) (caddar 4) (cadddr 4) + (cdaaar 4) (cdaadr 4) (cdadar 4) (cdaddr 4) + (cddaar 4) (cddadr 4) (cdddar 4) (cddddr 4))) + + +(defvar *estimate-code-size-punt* + '(block + do do* dolist + flet + labels lambda let let* locally + macrolet multiple-value-bind + prog prog* + symbol-macrolet + tagbody + unwind-protect + with-open-file)) + + +(defun destructuring-size (x) + (do ((x x (cdr x)) (n 0 (+ (destructuring-size (car x)) n))) + ((atom x) (+ n (if (null x) 0 1))))) + + +(defun estimate-code-size (x env) + (catch 'estimate-code-size + (estimate-code-size-1 x env))) + + +(defun estimate-code-size-1 (x env) + (flet ((list-size (l) + (let ((n 0)) + (declare (fixnum n)) + (dolist (x l n) (incf n (estimate-code-size-1 x env)))))) + ;;@@@@ ???? (declare (function list-size (list) fixnum)) + (cond ((constantp x #+Genera env) 1) + ((symbolp x) (multiple-value-bind (new-form expanded-p) (macroexpand-1 x env) + (if expanded-p (estimate-code-size-1 new-form env) 1))) + ((atom x) 1) ;??? self-evaluating??? + ((symbolp (car x)) + (let ((fn (car x)) (tem nil) (n 0)) + (declare (symbol fn) (fixnum n)) + (macrolet ((f (overhead &optional (args nil args-p)) + `(the fixnum (+ (the fixnum ,overhead) + (the fixnum (list-size ,(if args-p args '(cdr x)))))))) + (cond ((setq tem (get fn 'estimate-code-size)) + (typecase tem + (fixnum (f tem)) + (t (funcall tem x env)))) + ((setq tem (assoc fn *special-code-sizes*)) (f (second tem))) + #+Genera + ((eq fn 'compiler:invisible-references) (list-size (cddr x))) + ((eq fn 'cond) + (dolist (clause (cdr x) n) (incf n (list-size clause)) (incf n))) + ((eq fn 'desetq) + (do ((l (cdr x) (cdr l))) ((null l) n) + (setq n (+ n (destructuring-size (car l)) (estimate-code-size-1 (cadr l) env))))) + ((member fn '(setq psetq)) + (do ((l (cdr x) (cdr l))) ((null l) n) + (setq n (+ n (estimate-code-size-1 (cadr l) env) 1)))) + ((eq fn 'go) 1) + ((eq fn 'function) + ;;This skirts the issue of implementationally-defined lambda macros + ;; by recognizing CL function names and nothing else. + #-cmu 1 + #+cmu (if (ext:valid-function-name-p (cadr x)) + 1 + (throw 'duplicatable-code-p nil))) + ((eq fn 'multiple-value-setq) (f (length (second x)) (cddr x))) + ((eq fn 'return-from) (1+ (estimate-code-size-1 (third x) env))) + ((or (special-operator-p fn) (member fn *estimate-code-size-punt*)) + (throw 'estimate-code-size nil)) + (t (multiple-value-bind (new-form expanded-p) (macroexpand-1 x env) + (if expanded-p + (estimate-code-size-1 new-form env) + (f 3)))))))) + (t (throw 'estimate-code-size nil))))) + + +;;;; Loop Errors + + +(defun loop-context () + (do ((l *loop-source-context* (cdr l)) (new nil (cons (car l) new))) + ((eq l (cdr *loop-source-code*)) (nreverse new)))) + + +(defun loop-error (format-string &rest format-args) + #+(or Genera CLOE) (declare (dbg:error-reporter)) + #+Genera (setq format-args (copy-list format-args)) ;Don't ask. + #+cmu + (kernel:simple-program-error "~?~%Current LOOP context:~{ ~S~}." + format-string format-args (loop-context)) + #-cmu + (error "~?~%Current LOOP context:~{ ~S~}." + format-string format-args (loop-context))) + + +(defun loop-warn (format-string &rest format-args) + (warn "~?~%Current LOOP context:~{ ~S~}." format-string format-args (loop-context))) + + +(defun loop-check-data-type (specified-type required-type + &optional (default-type required-type)) + (if (null specified-type) + default-type + (multiple-value-bind (a b) (subtypep specified-type required-type) + (cond ((not b) + (loop-warn "LOOP couldn't verify that ~S is a subtype of the required type ~S." + specified-type required-type)) + ((not a) + (loop-error "Specified data type ~S is not a subtype of ~S." + specified-type required-type))) + specified-type))) + + +;;;INTERFACE: Traditional, ANSI, Lucid. +(defmacro loop-finish () + "Causes the iteration to terminate \"normally\", the same as implicit +termination by an iteration driving clause, or by use of WHILE or +UNTIL -- the epilogue code (if any) will be run, and any implicitly +collected result will be returned as the value of the LOOP." + '(go end-loop)) + + + +(defun subst-gensyms-for-nil (tree) + (declare (special *ignores*)) + (cond + ((null tree) (car (push (loop-gentemp) *ignores*))) + ((atom tree) tree) + (t (cons (subst-gensyms-for-nil (car tree)) + (subst-gensyms-for-nil (cdr tree)))))) + +(defun loop-build-destructuring-bindings (crocks forms) + (if crocks + (let ((*ignores* ())) + (declare (special *ignores*)) + `((destructuring-bind ,(subst-gensyms-for-nil (car crocks)) + ,(cadr crocks) + (declare (ignore ,@*ignores*)) + ,@(loop-build-destructuring-bindings (cddr crocks) forms)))) + forms)) + +(defun loop-translate (*loop-source-code* *loop-macro-environment* *loop-universe*) + (let ((*loop-original-source-code* *loop-source-code*) + (*loop-source-context* nil) + (*loop-iteration-variables* nil) + (*loop-variables* nil) + (*loop-nodeclare* nil) + (*loop-named-variables* nil) + (*loop-declarations* nil) + (*loop-desetq-crocks* nil) + (*loop-bind-stack* nil) + (*loop-prologue* nil) + (*loop-wrappers* nil) + (*loop-before-loop* nil) + (*loop-body* nil) + (*loop-emitted-body* nil) + (*loop-after-body* nil) + (*loop-epilogue* nil) + (*loop-after-epilogue* nil) + (*loop-final-value-culprit* nil) + (*loop-inside-conditional* nil) + (*loop-when-it-variable* nil) + (*loop-never-stepped-variable* nil) + (*loop-names* nil) + (*loop-collection-cruft* nil)) + (loop-iteration-driver) + (loop-bind-block) + (let ((answer `(loop-body + ,(nreverse *loop-prologue*) + ,(nreverse *loop-before-loop*) + ,(nreverse *loop-body*) + ,(nreverse *loop-after-body*) + ,(nreconc *loop-epilogue* (nreverse *loop-after-epilogue*))))) + (dolist (entry *loop-bind-stack*) + (let ((vars (first entry)) + (dcls (second entry)) + (crocks (third entry)) + (wrappers (fourth entry))) + (dolist (w wrappers) + (setq answer (append w (list answer)))) + (when (or vars dcls crocks) + (let ((forms (list answer))) + ;;(when crocks (push crocks forms)) + (when dcls (push `(declare ,@dcls) forms)) + (setq answer `(,(cond ((not vars) 'locally) + (*loop-destructuring-hooks* (first *loop-destructuring-hooks*)) + (t 'let)) + ,vars + ,@(loop-build-destructuring-bindings crocks forms))))))) + (if *loop-names* + (do () ((null (car *loop-names*)) answer) + (setq answer `(block ,(pop *loop-names*) ,answer))) + `(block nil ,answer))))) + + +(defun loop-iteration-driver () + (do () ((null *loop-source-code*)) + (let ((keyword (car *loop-source-code*)) (tem nil)) + (cond ((not (symbolp keyword)) + (loop-error "~S found where LOOP keyword expected." keyword)) + (t (setq *loop-source-context* *loop-source-code*) + (loop-pop-source) + (cond ((setq tem (loop-lookup-keyword keyword (loop-universe-keywords *loop-universe*))) + ;;It's a "miscellaneous" toplevel LOOP keyword (do, collect, named, etc.) + (apply (symbol-function (first tem)) (rest tem))) + ((setq tem (loop-lookup-keyword keyword (loop-universe-iteration-keywords *loop-universe*))) + (loop-hack-iteration tem)) + ((loop-tmember keyword '(and else)) + ;; Alternative is to ignore it, ie let it go around to the next keyword... + (loop-error "Secondary clause misplaced at top level in LOOP macro: ~S ~S ~S ..." + keyword (car *loop-source-code*) (cadr *loop-source-code*))) + (t (loop-error "~S is an unknown keyword in LOOP macro." keyword)))))))) + + + +(defun loop-pop-source () + (if *loop-source-code* + (pop *loop-source-code*) + (loop-error "LOOP source code ran out when another token was expected."))) + + +(defun loop-get-compound-form () + (let ((form (loop-get-form))) + (unless (consp form) + (loop-error "Compound form expected, but found ~A." form)) + form)) + +(defun loop-get-progn () + (do ((forms (list (loop-get-compound-form)) + (cons (loop-get-compound-form) forms)) + (nextform (car *loop-source-code*) + (car *loop-source-code*))) + ((atom nextform) + (if (null (cdr forms)) (car forms) (cons 'progn (nreverse forms)))))) + + +(defun loop-get-form () + (if *loop-source-code* + (loop-pop-source) + (loop-error "LOOP code ran out where a form was expected."))) + + +(defun loop-construct-return (form) + `(return-from ,(car *loop-names*) ,form)) + + +(defun loop-pseudo-body (form) + (cond ((or *loop-emitted-body* *loop-inside-conditional*) (push form *loop-body*)) + (t (push form *loop-before-loop*) (push form *loop-after-body*)))) + +(defun loop-emit-body (form) + (setq *loop-emitted-body* t) + (loop-pseudo-body form)) + +(defun loop-emit-final-value (&optional (form nil form-supplied-p)) + (when form-supplied-p + (push (loop-construct-return form) *loop-after-epilogue*)) + (when *loop-final-value-culprit* + (loop-warn "LOOP clause is providing a value for the iteration,~@ + however one was already established by a ~S clause." + *loop-final-value-culprit*)) + (setq *loop-final-value-culprit* (car *loop-source-context*))) + + +(defun loop-disallow-conditional (&optional kwd) + #+(or Genera CLOE) (declare (dbg:error-reporter)) + (when *loop-inside-conditional* + (loop-error "~:[This LOOP~;The LOOP ~:*~S~] clause is not permitted inside a conditional." kwd))) + +(defun loop-disallow-anonymous-collectors () + (when (find-if-not 'loop-collector-name *loop-collection-cruft*) + (loop-error "This LOOP clause is not permitted with anonymous collectors."))) + +(defun loop-disallow-aggregate-booleans () + (when (loop-tmember *loop-final-value-culprit* '(always never thereis)) + (loop-error "This anonymous collection LOOP clause is not permitted with aggregate booleans."))) + + + +;;;; Loop Types + + +(defun loop-typed-init (data-type) + (when (and data-type (subtypep data-type 'number)) + (if (or (subtypep data-type 'float) (subtypep data-type '(complex float))) + (coerce 0 data-type) + 0))) + + +(defun loop-optional-type (&optional variable) + ;;No variable specified implies that no destructuring is permissible. + (and *loop-source-code* ;Don't get confused by NILs... + (let ((z (car *loop-source-code*))) + (cond ((loop-tequal z 'of-type) + ;;This is the syntactically unambigous form in that the form of the + ;; type specifier does not matter. Also, it is assumed that the + ;; type specifier is unambiguously, and without need of translation, + ;; a common lisp type specifier or pattern (matching the variable) thereof. + (loop-pop-source) + (loop-pop-source)) + + ((symbolp z) + ;;This is the (sort of) "old" syntax, even though we didn't used to support all of + ;; these type symbols. + (let ((type-spec (or (gethash z (loop-universe-type-symbols *loop-universe*)) + (gethash (symbol-name z) (loop-universe-type-keywords *loop-universe*))))) + (when type-spec + (loop-pop-source) + type-spec))) + (t + ;;This is our sort-of old syntax. But this is only valid for when we are destructuring, + ;; so we will be compulsive (should we really be?) and require that we in fact be + ;; doing variable destructuring here. We must translate the old keyword pattern typespec + ;; into a fully-specified pattern of real type specifiers here. + (if (consp variable) + (unless (consp z) + (loop-error + "~S found where a LOOP keyword, LOOP type keyword, or LOOP type pattern expected." + z)) + (loop-error "~S found where a LOOP keyword or LOOP type keyword expected." z)) + (loop-pop-source) + (labels ((translate (k v) + (cond ((null k) nil) + ((atom k) + (replicate + (or (gethash k (loop-universe-type-symbols *loop-universe*)) + (gethash (symbol-name k) (loop-universe-type-keywords *loop-universe*)) + (loop-error + "Destructuring type pattern ~S contains unrecognized type keyword ~S." + z k)) + v)) + ((atom v) + (loop-error + "Destructuring type pattern ~S doesn't match variable pattern ~S." + z variable)) + (t (cons (translate (car k) (car v)) (translate (cdr k) (cdr v)))))) + (replicate (typ v) + (if (atom v) typ (cons (replicate typ (car v)) (replicate typ (cdr v)))))) + (translate z variable))))))) + + + +;;;; Loop Variables + + +(defun loop-bind-block () + (when (or *loop-variables* *loop-declarations* *loop-wrappers*) + (push (list (nreverse *loop-variables*) *loop-declarations* *loop-desetq-crocks* *loop-wrappers*) + *loop-bind-stack*) + (setq *loop-variables* nil + *loop-declarations* nil + *loop-desetq-crocks* nil + *loop-wrappers* nil))) + +(defun loop-variable-p (name) + (do ((entry *loop-bind-stack* (cdr entry))) (nil) + (cond ((null entry) + (return nil)) + ((assoc name (caar entry) :test #'eq) + (return t))))) + +(defun loop-make-variable (name initialization dtype &optional iteration-variable-p) + (cond ((null name) + (cond ((not (null initialization)) + (push (list (setq name (loop-gentemp 'loop-ignore-)) + initialization) + *loop-variables*) + (push `(ignore ,name) *loop-declarations*)))) + ((atom name) + (cond (iteration-variable-p + (if (member name *loop-iteration-variables*) + (loop-error "Duplicated LOOP iteration variable ~S." name) + (push name *loop-iteration-variables*))) + ((assoc name *loop-variables*) + (loop-error "Duplicated variable ~S in LOOP parallel binding." name))) + (unless (symbolp name) + (loop-error "Bad variable ~S somewhere in LOOP." name)) + (loop-declare-variable name dtype) + ;; We use ASSOC on this list to check for duplications (above), + ;; so don't optimize out this list: + (push (list name (or initialization (loop-typed-init dtype))) + *loop-variables*)) + (initialization + (cond (*loop-destructuring-hooks* + (loop-declare-variable name dtype) + (push (list name initialization) *loop-variables*)) + (t (let ((newvar (loop-gentemp 'loop-destructure-))) + (loop-declare-variable name dtype) + (push (list newvar initialization) *loop-variables*) + ;; *LOOP-DESETQ-CROCKS* gathered in reverse order. + (setq *loop-desetq-crocks* + (list* name newvar *loop-desetq-crocks*)) + #+ignore + (loop-make-variable name nil dtype iteration-variable-p))))) + (t (let ((tcar nil) (tcdr nil)) + (if (atom dtype) (setq tcar (setq tcdr dtype)) + (setq tcar (car dtype) tcdr (cdr dtype))) + (loop-make-variable (car name) nil tcar iteration-variable-p) + (loop-make-variable (cdr name) nil tcdr iteration-variable-p)))) + name) + + +(defun loop-make-iteration-variable (name initialization dtype) + (when (and name (loop-variable-p name)) + (loop-error "Variable ~S has already been used" name)) + (loop-make-variable name initialization dtype t)) + + +(defun loop-declare-variable (name dtype) + (cond ((or (null name) (null dtype) (eq dtype t)) nil) + ((symbolp name) + (unless (or (eq dtype t) (member (the symbol name) *loop-nodeclare*)) + (let ((dtype (let ((init (loop-typed-init dtype))) + (if (typep init dtype) + dtype + `(or (member ,init) ,dtype))))) + (push `(type ,dtype ,name) *loop-declarations*)))) + ((consp name) + (cond ((consp dtype) + (loop-declare-variable (car name) (car dtype)) + (loop-declare-variable (cdr name) (cdr dtype))) + (t (loop-declare-variable (car name) dtype) + (loop-declare-variable (cdr name) dtype)))) + (t (error "Invalid LOOP variable passed in: ~S." name)))) + + +(defun loop-maybe-bind-form (form data-type) + (if (loop-constantp form) + form + (loop-make-variable (loop-gentemp 'loop-bind-) form data-type))) + + + +(defun loop-do-if (for negatep) + (let ((form (loop-get-form)) + (it-p nil) + (first-clause-p t) then else) + (let ((*loop-inside-conditional* t)) + (flet ((get-clause (for) + (do ((body nil)) (nil) + (let ((key (car *loop-source-code*)) (*loop-body* nil) data) + (cond ((not (symbolp key)) + (loop-error + "~S found where keyword expected getting LOOP clause after ~S." + key for)) + (t (setq *loop-source-context* *loop-source-code*) + (loop-pop-source) + (when (and (loop-tequal (car *loop-source-code*) 'it) + first-clause-p) + (setq *loop-source-code* + (cons (or it-p (setq it-p (loop-when-it-variable))) + (cdr *loop-source-code*)))) + (cond ((or (not (setq data (loop-lookup-keyword + key (loop-universe-keywords *loop-universe*)))) + (progn (apply (symbol-function (car data)) (cdr data)) + (null *loop-body*))) + (loop-error + "~S does not introduce a LOOP clause that can follow ~S." + key for)) + (t (setq body (nreconc *loop-body* body))))))) + (setq first-clause-p nil) + (if (loop-tequal (car *loop-source-code*) :and) + (loop-pop-source) + (return (if (cdr body) `(progn ,@(nreverse body)) (car body))))))) + (setq then (get-clause for)) + (setq else (when (loop-tequal (car *loop-source-code*) :else) + (loop-pop-source) + (list (get-clause :else))))) + (when (loop-tequal (car *loop-source-code*) :end) + (loop-pop-source)) + (when it-p + (setq form `(setq ,it-p ,form)))) + (loop-pseudo-body + `(if ,(if negatep `(not ,form) form) + ,then + ,@else)))) + + +(defun loop-do-initially () + (loop-disallow-conditional :initially) + (push (loop-get-progn) *loop-prologue*)) + +(defun loop-do-finally () + (loop-disallow-conditional :finally) + (push (loop-get-progn) *loop-epilogue*)) + +(defun loop-do-do () + (loop-emit-body (loop-get-progn))) + +(defun loop-do-named () + (let ((name (loop-pop-source))) + (unless (symbolp name) + (loop-error "~S is an invalid name for your LOOP." name)) + (when (or *loop-before-loop* *loop-body* *loop-after-epilogue* *loop-inside-conditional*) + (loop-error "The NAMED ~S clause occurs too late." name)) + (when *loop-names* + (loop-error "You may only use one NAMED clause in your loop: NAMED ~S ... NAMED ~S." + (car *loop-names*) name)) + (setq *loop-names* (list name nil)))) + +(defun loop-do-return () + (loop-pseudo-body (loop-construct-return (loop-get-form)))) + + +;;;; Value Accumulation: List + + +(defstruct (loop-collector + (:copier nil) + (:predicate nil)) + name + class + (history nil) + (tempvars nil) + dtype + (data nil)) ;collector-specific data + + +(defun loop-get-collection-info (collector class default-type) + (let ((form (loop-get-form)) + (dtype (and (not (loop-universe-ansi *loop-universe*)) (loop-optional-type))) + (name (when (loop-tequal (car *loop-source-code*) 'into) + (loop-pop-source) + (loop-pop-source)))) + (when (not (symbolp name)) + (loop-error "Value accumulation recipient name, ~S, is not a symbol." name)) + (unless name + (loop-disallow-aggregate-booleans)) + (unless dtype + (setq dtype (or (loop-optional-type) default-type))) + (let ((cruft (find (the symbol name) *loop-collection-cruft* + :key #'loop-collector-name))) + (cond ((not cruft) + (when (and name (loop-variable-p name)) + (loop-error "Variable ~S cannot be used in INTO clause" name)) + (push (setq cruft (make-loop-collector + :name name :class class + :history (list collector) :dtype dtype)) + *loop-collection-cruft*)) + (t (unless (eq (loop-collector-class cruft) class) + (loop-error + "Incompatible kinds of LOOP value accumulation specified for collecting~@ + ~:[as the value of the LOOP~;~:*INTO ~S~]: ~S and ~S." + name (car (loop-collector-history cruft)) collector)) + (unless (equal dtype (loop-collector-dtype cruft)) + (loop-warn + "Unequal datatypes specified in different LOOP value accumulations~@ + into ~S: ~S and ~S." + name dtype (loop-collector-dtype cruft)) + (when (eq (loop-collector-dtype cruft) t) + (setf (loop-collector-dtype cruft) dtype))) + (push collector (loop-collector-history cruft)))) + (values cruft form)))) + + +(defun loop-list-collection (specifically) ;NCONC, LIST, or APPEND + (multiple-value-bind (lc form) (loop-get-collection-info specifically 'list 'list) + (let ((tempvars (loop-collector-tempvars lc))) + (unless tempvars + (setf (loop-collector-tempvars lc) + (setq tempvars (list* (loop-gentemp 'loop-list-head-) + (loop-gentemp 'loop-list-tail-) + (and (loop-collector-name lc) + (list (loop-collector-name lc)))))) + (push `(with-loop-list-collection-head ,tempvars) *loop-wrappers*) + (unless (loop-collector-name lc) + (loop-emit-final-value `(loop-collect-answer ,(car tempvars) ,@(cddr tempvars))))) + (ecase specifically + (list (setq form `(list ,form))) + (nconc nil) + (append (unless (and (consp form) (eq (car form) 'list)) + (setq form `(loop-copylist* ,form))))) + (loop-emit-body `(loop-collect-rplacd ,tempvars ,form))))) + + +;;;; Value Accumulation: max, min, sum, count. + + + +(defun loop-sum-collection (specifically required-type default-type) ;SUM, COUNT + (multiple-value-bind (lc form) + (loop-get-collection-info specifically 'sum default-type) + (loop-check-data-type (loop-collector-dtype lc) required-type) + (let ((tempvars (loop-collector-tempvars lc))) + (unless tempvars + (setf (loop-collector-tempvars lc) + (setq tempvars (list (loop-make-variable + (or (loop-collector-name lc) + (loop-gentemp 'loop-sum-)) + nil (loop-collector-dtype lc))))) + (unless (loop-collector-name lc) + (loop-emit-final-value (car (loop-collector-tempvars lc))))) + (loop-emit-body + (if (eq specifically 'count) + `(when ,form + (setq ,(car tempvars) + ,(hide-variable-reference t (car tempvars) `(1+ ,(car tempvars))))) + `(setq ,(car tempvars) + (+ ,(hide-variable-reference t (car tempvars) (car tempvars)) + ,form))))))) + + + +(defun loop-maxmin-collection (specifically) + (multiple-value-bind (lc form) + (loop-get-collection-info specifically 'maxmin *loop-real-data-type*) + (loop-check-data-type (loop-collector-dtype lc) *loop-real-data-type*) + (let ((data (loop-collector-data lc))) + (unless data + (setf (loop-collector-data lc) + (setq data (make-loop-minimax + (or (loop-collector-name lc) (loop-gentemp 'loop-maxmin-)) + (loop-collector-dtype lc)))) + (unless (loop-collector-name lc) + (loop-emit-final-value (loop-minimax-answer-variable data)))) + (loop-note-minimax-operation specifically data) + (push `(with-minimax-value ,data) *loop-wrappers*) + (loop-emit-body `(loop-accumulate-minimax-value ,data ,specifically ,form)) + ))) + + +;;;; Value Accumulation: Aggregate Booleans + +;;;ALWAYS and NEVER. +;;; Under ANSI these are not permitted to appear under conditionalization. +(defun loop-do-always (restrictive negate) + (let ((form (loop-get-form))) + (when restrictive (loop-disallow-conditional)) + (loop-disallow-anonymous-collectors) + (loop-emit-body `(,(if negate 'when 'unless) ,form + ,(loop-construct-return nil))) + (loop-emit-final-value t))) + + + +;;;THERIS. +;;; Under ANSI this is not permitted to appear under conditionalization. +(defun loop-do-thereis (restrictive) + (when restrictive (loop-disallow-conditional)) + (loop-disallow-anonymous-collectors) + (loop-emit-final-value) + (loop-emit-body `(when (setq ,(loop-when-it-variable) ,(loop-get-form)) + ,(loop-construct-return *loop-when-it-variable*)))) + + +(defun loop-do-while (negate kwd &aux (form (loop-get-form))) + (loop-disallow-conditional kwd) + (loop-pseudo-body `(,(if negate 'when 'unless) ,form (go end-loop)))) + + +(defun loop-do-with () + (loop-disallow-conditional :with) + (do ((var) (val) (dtype)) (nil) + (setq var (loop-pop-source) + dtype (loop-optional-type var) + val (cond ((loop-tequal (car *loop-source-code*) :=) + (loop-pop-source) + (loop-get-form)) + (t nil))) + (when (and var (loop-variable-p var)) + (loop-error "Variable ~S has already been used" var)) + (loop-make-variable var val dtype) + (if (loop-tequal (car *loop-source-code*) :and) + (loop-pop-source) + (return (loop-bind-block))))) + + +;;;; The iteration driver + +(defun loop-hack-iteration (entry) + (flet ((make-endtest (list-of-forms) + (cond ((null list-of-forms) nil) + ((member t list-of-forms) '(go end-loop)) + (t `(when ,(if (null (cdr (setq list-of-forms (nreverse list-of-forms)))) + (car list-of-forms) + (cons 'or list-of-forms)) + (go end-loop)))))) + (do ((pre-step-tests nil) + (steps nil) + (post-step-tests nil) + (pseudo-steps nil) + (pre-loop-pre-step-tests nil) + (pre-loop-steps nil) + (pre-loop-post-step-tests nil) + (pre-loop-pseudo-steps nil) + (tem) (data)) + (nil) + ;; Note we collect endtests in reverse order, but steps in correct + ;; order. MAKE-ENDTEST does the nreverse for us. + (setq tem (setq data (apply (symbol-function (first entry)) (rest entry)))) + (and (car tem) (push (car tem) pre-step-tests)) + (setq steps (nconc steps (loop-copylist* (car (setq tem (cdr tem)))))) + (and (car (setq tem (cdr tem))) (push (car tem) post-step-tests)) + (setq pseudo-steps (nconc pseudo-steps (loop-copylist* (car (setq tem (cdr tem)))))) + (setq tem (cdr tem)) + (when *loop-emitted-body* + (loop-error "Iteration in LOOP follows body code.")) + (unless tem (setq tem data)) + (when (car tem) (push (car tem) pre-loop-pre-step-tests)) + (setq pre-loop-steps (nconc pre-loop-steps (loop-copylist* (car (setq tem (cdr tem)))))) + (when (car (setq tem (cdr tem))) (push (car tem) pre-loop-post-step-tests)) + (setq pre-loop-pseudo-steps (nconc pre-loop-pseudo-steps (loop-copylist* (cadr tem)))) + (unless (loop-tequal (car *loop-source-code*) :and) + (setq *loop-before-loop* (list* (loop-make-desetq pre-loop-pseudo-steps) + (make-endtest pre-loop-post-step-tests) + (loop-make-psetq pre-loop-steps) + (make-endtest pre-loop-pre-step-tests) + *loop-before-loop*) + *loop-after-body* (list* (loop-make-desetq pseudo-steps) + (make-endtest post-step-tests) + (loop-make-psetq steps) + (make-endtest pre-step-tests) + *loop-after-body*)) + (loop-bind-block) + (return nil)) + (loop-pop-source) ; flush the "AND" + (when (and (not (loop-universe-implicit-for-required *loop-universe*)) + (setq tem (loop-lookup-keyword + (car *loop-source-code*) + (loop-universe-iteration-keywords *loop-universe*)))) + ;;Latest ANSI clarification is that the FOR/AS after the AND must NOT be supplied. + (loop-pop-source) + (setq entry tem))))) + + +;;;; Main Iteration Drivers + + +;FOR variable keyword ..args.. +(defun loop-do-for () + (let* ((var (or (loop-pop-source) (loop-gentemp 'loop-do-for-anon-))) + (data-type (loop-optional-type var)) + (keyword (loop-pop-source)) + (first-arg nil) + (tem nil)) + (setq first-arg (loop-get-form)) + (unless (and (symbolp keyword) + (setq tem (loop-lookup-keyword + keyword + (loop-universe-for-keywords *loop-universe*)))) + (loop-error "~S is an unknown keyword in FOR or AS clause in LOOP." keyword)) + (apply (car tem) var first-arg data-type (cdr tem)))) + +(defun loop-do-repeat () + (loop-disallow-conditional :repeat) + (let ((form (loop-get-form)) + (type 'real)) + (let ((var (loop-make-variable (loop-gentemp) form type))) + (push `(when (minusp (decf ,var)) (go end-loop)) *loop-before-loop*) + (push `(when (minusp (decf ,var)) (go end-loop)) *loop-after-body*) + ;; FIXME: What should + ;; (loop count t into a + ;; repeat 3 + ;; count t into b + ;; finally (return (list a b))) + ;; return: (3 3) or (4 3)? PUSHes above are for the former + ;; variant, L-P-B below for the latter. + #+nil (loop-pseudo-body `(when (minusp (decf ,var)) (go end-loop)))))) + +(defun loop-when-it-variable () + (or *loop-when-it-variable* + (setq *loop-when-it-variable* + (loop-make-variable (loop-gentemp 'loop-it-) nil nil)))) + + +;;;; Various FOR/AS Subdispatches + + +;;;ANSI "FOR x = y [THEN z]" is sort of like the old Genera one when the THEN +;;; is omitted (other than being more stringent in its placement), and like +;;; the old "FOR x FIRST y THEN z" when the THEN is present. I.e., the first +;;; initialization occurs in the loop body (first-step), not in the variable binding +;;; phase. +(defun loop-ansi-for-equals (var val data-type) + (loop-make-iteration-variable var nil data-type) + (cond ((loop-tequal (car *loop-source-code*) :then) + ;;Then we are the same as "FOR x FIRST y THEN z". + (loop-pop-source) + `(() (,var ,(loop-get-form)) () () + () (,var ,val) () ())) + (t ;;We are the same as "FOR x = y". + ;; Let me document here what this is returning. Look at + ;; loop-hack-iteration for more info. But anyway, we return a list of + ;; 8 items, in this order: PRE-STEP-TESTS, STEPS, POST-STEP-TESTS, + ;; PSEUDO-STEPS, PRE-LOOP-PRE-STEP-TESTS, PRE-LOOP-STEPS, + ;; PRE-LOOP-POST-STEP-TESTS, PRE-LOOP-PSEUDO-STEPS. (We should add + ;; something to make it easier to figure out what these args are!) + ;; + ;; For a "FOR x = y" clause without the THEN, we want the STEPS item to + ;; step the variable VAR with the value VAL. This gets placed in the + ;; body of the loop. The original code just did that. It seems that + ;; the STEPS form is placed in *loop-before-loop* and in + ;; *loop-after-loop*. Loop optimization would then see the same form + ;; in both, and move them into the beginning of body. This is ok, + ;; except that if there are :initially forms that were placed into the + ;; loop prologue, the :initially forms might refer to incorrectly + ;; initialized variables, because the optimizer moved STEPS from from + ;; *loop-before-loop* into the body. + ;; + ;; To solve this, we add a PRE-LOOP-PSEUDO-STEP form that is identical + ;; to the STEPS form. This gets placed in *loop-before-loop*. But + ;; this won't match any *loop-after-loop* form, so it won't get moved, + ;; and we maintain the proper sequencing such that the + ;; PRE-LOOP-PSEUDO-STEP form is in *loop-before-loop*, before any + ;; :initially clauses that might refer to this. So all is well. Whew. + ;; + ;; I hope this doesn't break anything else. + `(() (,var ,val) () () + () () () (,var ,val)) + ))) + + +(defun loop-for-across (var val data-type) + (loop-make-iteration-variable var nil data-type) + (let ((vector-var (loop-gentemp 'loop-across-vector-)) + (index-var (loop-gentemp 'loop-across-index-))) + (multiple-value-bind (vector-form constantp vector-value) + (loop-constant-fold-if-possible val 'vector) + (loop-make-variable + vector-var vector-form + (if (and (consp vector-form) (eq (car vector-form) 'the)) + (cadr vector-form) + 'vector)) + #+Genera (push `(system:array-register ,vector-var) *loop-declarations*) + (loop-make-variable index-var 0 'fixnum) + (let* ((length 0) + (length-form (cond ((not constantp) + (let ((v (loop-gentemp 'loop-across-limit-))) + ;; This used to just push the length + ;; computation into the prologue code. I + ;; (rtoy) don't think that's right, + ;; especially since the prologue is supposed + ;; to happen AFTER other initializations. + ;; So, this puts the computation in + ;; *loop-before-body*. We need a matching + ;; entry for *loop-after-body*, so stuff a + ;; NIL there. + (push `(setq ,v (length ,vector-var)) *loop-before-loop*) + (push nil *loop-after-body*) + (loop-make-variable v 0 'fixnum))) + (t (setq length (length vector-value))))) + (first-test `(>= ,index-var ,length-form)) + (other-test first-test) + (step `(,var (aref ,vector-var ,index-var))) + (pstep `(,index-var (1+ ,index-var)))) + (declare (fixnum length)) + (when constantp + (setq first-test (= length 0)) + (when (<= length 1) + (setq other-test t))) + `(,other-test ,step () ,pstep + ,@(and (not (eq first-test other-test)) `(,first-test ,step () ,pstep))))))) + + + +;;;; List Iteration + + +(defun loop-list-step (listvar) + ;;We are not equipped to analyze whether 'FOO is the same as #'FOO here in any + ;; sensible fashion, so let's give an obnoxious warning whenever 'FOO is used + ;; as the stepping function. + ;;While a Discerning Compiler may deal intelligently with (funcall 'foo ...), not + ;; recognizing FOO may defeat some LOOP optimizations. + (let ((stepper (cond ((loop-tequal (car *loop-source-code*) :by) + (loop-pop-source) + (loop-get-form)) + (t '(function cdr))))) + (cond ((and (consp stepper) (eq (car stepper) 'quote)) + (loop-warn "Use of QUOTE around stepping function in LOOP will be left verbatim.") + (values `(funcall ,stepper ,listvar) nil)) + ((and (consp stepper) (eq (car stepper) 'function)) + (values (list (cadr stepper) listvar) (cadr stepper))) + (t (values `(funcall ,(loop-make-variable (loop-gentemp 'loop-fn-) stepper 'function) + ,listvar) + nil))))) + + +(defun loop-for-on (var val data-type) + (multiple-value-bind (list constantp list-value) (loop-constant-fold-if-possible val) + (let ((listvar var)) + (cond ((and var (symbolp var)) (loop-make-iteration-variable var list data-type)) + (t (loop-make-variable (setq listvar (loop-gentemp)) list 't) + (loop-make-iteration-variable var nil data-type))) + (multiple-value-bind (list-step step-function) (loop-list-step listvar) + (declare #+(and (not LOOP-Prefer-POP) (not CLOE)) (ignore step-function)) + ;;@@@@ The CLOE problem above has to do with bug in macroexpansion of multiple-value-bind. + (let* ((first-endtest + (hide-variable-reference + (eq var listvar) + listvar + ;; the following should use `atom' instead of `endp', per + ;; [bug2428] + `(atom ,listvar))) + (other-endtest first-endtest)) + (when (and constantp (listp list-value)) + (setq first-endtest (null list-value))) + (cond ((eq var listvar) + ;;Contour of the loop is different because we use the user's variable... + `(() (,listvar ,(hide-variable-reference t listvar list-step)) ,other-endtest + () () () ,first-endtest ())) + #+LOOP-Prefer-POP + ((and step-function + (let ((n (cdr (assoc step-function '((cdr . 1) (cddr . 2) + (cdddr . 3) (cddddr . 4)))))) + (and n (do ((l var (cdr l)) (i 0 (1+ i))) + ((atom l) (and (null l) (= i n))) + (declare (fixnum i)))))) + (let ((step (mapcan #'(lambda (x) (list x `(pop ,listvar))) var))) + `(,other-endtest () () ,step ,first-endtest () () ,step))) + (t (let ((step `(,var ,listvar)) (pseudo `(,listvar ,list-step))) + `(,other-endtest ,step () ,pseudo + ,@(and (not (eq first-endtest other-endtest)) + `(,first-endtest ,step () ,pseudo))))))))))) + + +(defun loop-for-in (var val data-type) + (multiple-value-bind (list constantp list-value) (loop-constant-fold-if-possible val) + (let ((listvar (loop-gentemp 'loop-list-))) + (loop-make-iteration-variable var nil data-type) + (loop-make-variable listvar list 'list) + (multiple-value-bind (list-step step-function) (loop-list-step listvar) + #-LOOP-Prefer-POP (declare (ignore step-function)) + (let* ((first-endtest `(endp ,listvar)) + (other-endtest first-endtest) + (step `(,var (car ,listvar))) + (pseudo-step `(,listvar ,list-step))) + (when (and constantp (listp list-value)) + (setq first-endtest (null list-value))) + #+LOOP-Prefer-POP (when (eq step-function 'cdr) + (setq step `(,var (pop ,listvar)) pseudo-step nil)) + `(,other-endtest ,step () ,pseudo-step + ,@(and (not (eq first-endtest other-endtest)) + `(,first-endtest ,step () ,pseudo-step)))))))) + + +;;;; Iteration Paths + + +(defstruct (loop-path + (:copier nil) + (:predicate nil)) + names + preposition-groups + inclusive-permitted + function + user-data) + + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defun add-loop-path (names function universe &key preposition-groups inclusive-permitted user-data) + (unless (listp names) (setq names (list names))) + ;; Can't do this due to CLOS bootstrapping problems. + #-(or Genera (and CLOE Source-Bootstrap)) (check-type universe loop-universe) + (let ((ht (loop-universe-path-keywords universe)) + (lp (make-loop-path + :names (mapcar #'symbol-name names) + :function function + :user-data user-data + :preposition-groups (mapcar #'(lambda (x) (if (listp x) x (list x))) preposition-groups) + :inclusive-permitted inclusive-permitted))) + (dolist (name names) (setf (gethash (symbol-name name) ht) lp)) + lp))) + + +;;; Note: path functions are allowed to use loop-make-variable, hack +;;; the prologue, etc. +(defun loop-for-being (var val data-type) + ;; FOR var BEING each/the pathname prep-phrases using-stuff... + ;; each/the = EACH or THE. Not clear if it is optional, so I guess we'll warn. + (let ((path nil) + (data nil) + (inclusive nil) + (stuff nil) + (initial-prepositions nil)) + (cond ((loop-tmember val '(:each :the)) (setq path (loop-pop-source))) + ((loop-tequal (car *loop-source-code*) :and) + (loop-pop-source) + (setq inclusive t) + (unless (loop-tmember (car *loop-source-code*) '(:its :each :his :her)) + (loop-error "~S found where ITS or EACH expected in LOOP iteration path syntax." + (car *loop-source-code*))) + (loop-pop-source) + (setq path (loop-pop-source)) + (setq initial-prepositions `((:in ,val)))) + (t (loop-error "Unrecognizable LOOP iteration path syntax. Missing EACH or THE?"))) + (cond ((not (symbolp path)) + (loop-error "~S found where a LOOP iteration path name was expected." path)) + ((not (setq data (loop-lookup-keyword path (loop-universe-path-keywords *loop-universe*)))) + (loop-error "~S is not the name of a LOOP iteration path." path)) + ((and inclusive (not (loop-path-inclusive-permitted data))) + (loop-error "\"Inclusive\" iteration is not possible with the ~S LOOP iteration path." path))) + (let ((fun (loop-path-function data)) + (preps (nconc initial-prepositions + (loop-collect-prepositional-phrases (loop-path-preposition-groups data) t))) + (user-data (loop-path-user-data data))) + (when (symbolp fun) (setq fun (symbol-function fun))) + (setq stuff (if inclusive + (apply fun var data-type preps :inclusive t user-data) + (apply fun var data-type preps user-data)))) + (when *loop-named-variables* + (loop-error "Unused USING variables: ~S." *loop-named-variables*)) + ;; STUFF is now (bindings prologue-forms . stuff-to-pass-back). Protect the system from the user + ;; and the user from himself. + (unless (member (length stuff) '(6 10)) + (loop-error "Value passed back by LOOP iteration path function for path ~S has invalid length." + path)) + (do ((l (car stuff) (cdr l)) (x)) ((null l)) + (if (atom (setq x (car l))) + (loop-make-iteration-variable x nil nil) + (loop-make-iteration-variable (car x) (cadr x) (caddr x)))) + (setq *loop-prologue* (nconc (reverse (cadr stuff)) *loop-prologue*)) + (cddr stuff))) + + + +;;;INTERFACE: Lucid, exported. +;;; i.e., this is part of our extended ansi-loop interface. +(defun named-variable (name) + (let ((tem (loop-tassoc name *loop-named-variables*))) + (declare (list tem)) + (cond ((null tem) (values (loop-gentemp) nil)) + (t (setq *loop-named-variables* (delete tem *loop-named-variables*)) + (values (cdr tem) t))))) + + +(defun loop-collect-prepositional-phrases (preposition-groups &optional USING-allowed initial-phrases) + (flet ((in-group-p (x group) (car (loop-tmember x group)))) + (do ((token nil) + (prepositional-phrases initial-phrases) + (this-group nil nil) + (this-prep nil nil) + (disallowed-prepositions + (mapcan #'(lambda (x) + (loop-copylist* + (find (car x) preposition-groups :test #'in-group-p))) + initial-phrases)) + (used-prepositions (mapcar #'car initial-phrases))) + ((null *loop-source-code*) (nreverse prepositional-phrases)) + (declare (symbol this-prep)) + (setq token (car *loop-source-code*)) + (dolist (group preposition-groups) + (when (setq this-prep (in-group-p token group)) + (return (setq this-group group)))) + (cond (this-group + (when (member this-prep disallowed-prepositions) + (loop-error + (if (member this-prep used-prepositions) + "A ~S prepositional phrase occurs multiply for some LOOP clause." + "Preposition ~S used when some other preposition has subsumed it.") + token)) + (setq used-prepositions (if (listp this-group) + (append this-group used-prepositions) + (cons this-group used-prepositions))) + (loop-pop-source) + (push (list this-prep (loop-get-form)) prepositional-phrases)) + ((and USING-allowed (loop-tequal token 'using)) + (loop-pop-source) + (do ((z (loop-pop-source) (loop-pop-source)) (tem)) (nil) + (when (cadr z) + (if (setq tem (loop-tassoc (car z) *loop-named-variables*)) + (loop-error + "The variable substitution for ~S occurs twice in a USING phrase,~@ + with ~S and ~S." + (car z) (cadr z) (cadr tem)) + (push (cons (car z) (cadr z)) *loop-named-variables*))) + (when (or (null *loop-source-code*) (symbolp (car *loop-source-code*))) + (return nil)))) + (t (return (nreverse prepositional-phrases))))))) + + +;;;; Master Sequencer Function + + +(defun loop-sequencer (indexv indexv-type indexv-user-specified-p + variable variable-type + sequence-variable sequence-type + step-hack default-top + prep-phrases) + (let ((endform nil) ;Form (constant or variable) with limit value. + (sequencep nil) ;T if sequence arg has been provided. + (testfn nil) ;endtest function + (test nil) ;endtest form. + (stepby (1+ (or (loop-typed-init indexv-type) 0))) ;Our increment. + (stepby-constantp t) + (step nil) ;step form. + (dir nil) ;Direction of stepping: NIL, :UP, :DOWN. + (inclusive-iteration nil) ;T if include last index. + (start-given nil) ;T when prep phrase has specified start + (start-value nil) + (start-constantp nil) + (limit-given nil) ;T when prep phrase has specified end + (limit-constantp nil) + (limit-value nil) + ) + (when variable (loop-make-iteration-variable variable nil variable-type)) + (do ((l prep-phrases (cdr l)) (prep) (form) (odir)) ((null l)) + (setq prep (caar l) form (cadar l)) + (case prep + ((:of :in) + (setq sequencep t) + (loop-make-variable sequence-variable form sequence-type)) + ((:from :downfrom :upfrom) + (setq start-given t) + (cond ((eq prep :downfrom) (setq dir ':down)) + ((eq prep :upfrom) (setq dir ':up))) + (multiple-value-setq (form start-constantp start-value) + (loop-constant-fold-if-possible form indexv-type)) + (loop-make-iteration-variable indexv form indexv-type)) + ((:upto :to :downto :above :below) + (cond ((loop-tequal prep :upto) (setq inclusive-iteration (setq dir ':up))) + ((loop-tequal prep :to) (setq inclusive-iteration t)) + ((loop-tequal prep :downto) (setq inclusive-iteration (setq dir ':down))) + ((loop-tequal prep :above) (setq dir ':down)) + ((loop-tequal prep :below) (setq dir ':up))) + (setq limit-given t) + (multiple-value-setq (form limit-constantp limit-value) + (loop-constant-fold-if-possible form indexv-type)) + (setq endform (if limit-constantp + `',limit-value + (loop-make-variable + (loop-gentemp 'loop-limit-) form indexv-type)))) + (:by + (multiple-value-setq (form stepby-constantp stepby) + (loop-constant-fold-if-possible form indexv-type)) + (unless stepby-constantp + (loop-make-variable (setq stepby (loop-gentemp 'loop-step-by-)) form indexv-type))) + (t (loop-error + "~S invalid preposition in sequencing or sequence path.~@ + Invalid prepositions specified in iteration path descriptor or something?" + prep))) + (when (and odir dir (not (eq dir odir))) + (loop-error "Conflicting stepping directions in LOOP sequencing path")) + (setq odir dir)) + (when (and sequence-variable (not sequencep)) + (loop-error "Missing OF or IN phrase in sequence path")) + ;; Now fill in the defaults. + (unless start-given + (loop-make-iteration-variable + indexv + (setq start-constantp t start-value (or (loop-typed-init indexv-type) 0)) + indexv-type)) + (cond ((member dir '(nil :up)) + (when (or limit-given default-top) + (unless limit-given + (loop-make-variable (setq endform (loop-gentemp 'loop-seq-limit-)) + nil indexv-type) + (push `(setq ,endform ,default-top) *loop-prologue*)) + (setq testfn (if inclusive-iteration '> '>=))) + (setq step (if (eql stepby 1) `(1+ ,indexv) `(+ ,indexv ,stepby)))) + (t (unless start-given + (unless default-top + (loop-error "Don't know where to start stepping.")) + (push `(setq ,indexv (1- ,default-top)) *loop-prologue*)) + (when (and default-top (not endform)) + (setq endform (loop-typed-init indexv-type) inclusive-iteration t)) + (when endform (setq testfn (if inclusive-iteration '< '<=))) + (setq step (if (eql stepby 1) `(1- ,indexv) `(- ,indexv ,stepby))))) + (when testfn (setq test (hide-variable-reference t indexv `(,testfn ,indexv ,endform)))) + (when step-hack + (setq step-hack `(,variable ,(hide-variable-reference indexv-user-specified-p indexv step-hack)))) + (let ((first-test test) (remaining-tests test)) + (when (and stepby-constantp start-constantp limit-constantp) + (when (setq first-test (funcall (symbol-function testfn) start-value limit-value)) + (setq remaining-tests t))) + `(() (,indexv ,(hide-variable-reference t indexv step)) ,remaining-tests ,step-hack + () () ,first-test ,step-hack)))) + + +;;;; Interfaces to the Master Sequencer + + + +(defun loop-for-arithmetic (var val data-type kwd) + (loop-sequencer + var (loop-check-data-type data-type 'number) t + nil nil nil nil nil nil + (loop-collect-prepositional-phrases + '((:from :upfrom :downfrom) (:to :upto :downto :above :below) (:by)) + nil (list (list kwd val))))) + + +(defun loop-sequence-elements-path (variable data-type prep-phrases + &key fetch-function size-function sequence-type element-type) + (multiple-value-bind (indexv indexv-user-specified-p) (named-variable 'index) + (let ((sequencev (named-variable 'sequence))) + #+Genera (when (and sequencev + (symbolp sequencev) + sequence-type + (subtypep sequence-type 'vector) + (not (member (the symbol sequencev) *loop-nodeclare*))) + (push `(sys:array-register ,sequencev) *loop-declarations*)) + (list* nil nil ; dummy bindings and prologue + (loop-sequencer + indexv 'fixnum indexv-user-specified-p + variable (or data-type element-type) + sequencev sequence-type + `(,fetch-function ,sequencev ,indexv) `(,size-function ,sequencev) + prep-phrases))))) + + +;;;; Builtin LOOP Iteration Paths + + +#|| +(loop for v being the hash-values of ht do (print v)) +(loop for k being the hash-keys of ht do (print k)) +(loop for v being the hash-values of ht using (hash-key k) do (print (list k v))) +(loop for k being the hash-keys of ht using (hash-value v) do (print (list k v))) +||# + +(defun loop-hash-table-iteration-path (variable data-type prep-phrases &key which) + (check-type which (member hash-key hash-value)) + (cond ((or (cdr prep-phrases) (not (member (caar prep-phrases) '(:in :of)))) + (loop-error "Too many prepositions!")) + ((null prep-phrases) (loop-error "Missing OF or IN in ~S iteration path."))) + (let ((ht-var (loop-gentemp 'loop-hashtab-)) + (next-fn (loop-gentemp 'loop-hashtab-next-)) + (dummy-predicate-var nil) + (post-steps nil)) + (multiple-value-bind (other-var other-p) + (named-variable (if (eq which 'hash-key) 'hash-value 'hash-key)) + ;;@@@@ named-variable returns a second value of T if the name was actually + ;; specified, so clever code can throw away the gensym'ed up variable if + ;; it isn't really needed. + ;;The following is for those implementations in which we cannot put dummy NILs + ;; into multiple-value-setq variable lists. + #-Genera (setq other-p t + dummy-predicate-var (loop-when-it-variable)) + (let* ((key-var nil) + (val-var nil) + (temp-val-var (loop-gentemp 'loop-hash-val-temp-)) + (temp-key-var (loop-gentemp 'loop-hash-key-temp-)) + (temp-predicate-var (loop-gentemp 'loop-hash-predicate-var-)) + (variable (or variable (loop-gentemp))) + (bindings `((,variable nil ,data-type) + (,ht-var ,(cadar prep-phrases)) + ,@(and other-p other-var `((,other-var nil)))))) + (if (eq which 'hash-key) + (setq key-var variable val-var (and other-p other-var)) + (setq key-var (and other-p other-var) val-var variable)) + (push `(with-hash-table-iterator (,next-fn ,ht-var)) *loop-wrappers*) + (when (consp key-var) + (setq post-steps `(,key-var ,(setq key-var (loop-gentemp 'loop-hash-key-temp-)) + ,@post-steps)) + (push `(,key-var nil) bindings)) + (when (consp val-var) + (setq post-steps `(,val-var ,(setq val-var (loop-gentemp 'loop-hash-val-temp-)) + ,@post-steps)) + (push `(,val-var nil) bindings)) + `(,bindings ;bindings + () ;prologue + () ;pre-test + () ;parallel steps + (not + (multiple-value-bind (,temp-predicate-var ,temp-key-var ,temp-val-var) + (,next-fn) + ;; We use M-V-BIND instead of M-V-SETQ because we only + ;; want to assign values to the key and val vars when we + ;; are in the hash table. When we reach the end, + ;; TEMP-PREDICATE-VAR is NIL, and so are temp-key-var and + ;; temp-val-var. This might break any type declarations + ;; on the key and val vars. + (when ,temp-predicate-var + (setq ,val-var ,temp-val-var) + (setq ,key-var ,temp-key-var)) + (setq ,dummy-predicate-var ,temp-predicate-var) + )) ;post-test + ,post-steps))))) + + +(defun loop-package-symbols-iteration-path (variable data-type prep-phrases &key symbol-types) + (cond ((and prep-phrases (cdr prep-phrases)) + (loop-error "Too many prepositions!")) + ((and prep-phrases (not (member (caar prep-phrases) '(:in :of)))) + (loop-error "Unknow preposition ~S" (caar prep-phrases)))) + (unless (symbolp variable) + (loop-error "Destructuring is not valid for package symbol iteration.")) + (let ((pkg-var (loop-gentemp 'loop-pkgsym-)) + (next-fn (loop-gentemp 'loop-pkgsym-next-)) + (variable (or variable (loop-gentemp))) + (pkg (or (cadar prep-phrases) '*package*))) + (push `(with-package-iterator (,next-fn ,pkg-var ,@symbol-types)) *loop-wrappers*) + `(((,variable nil ,data-type) (,pkg-var ,pkg)) + () + () + () + (not (multiple-value-setq (,(progn + ;;@@@@ If an implementation can get away without actually + ;; using a variable here, so much the better. + #+Genera NIL + #-Genera (loop-when-it-variable)) + ,variable) + (,next-fn))) + ()))) + +;;;; ANSI Loop + +(eval-when (:compile-toplevel :load-toplevel :execute) + + (defun make-ansi-loop-universe (extended-p) + (let ((w (make-standard-loop-universe + :keywords `((named (loop-do-named)) + (initially (loop-do-initially)) + (finally (loop-do-finally)) + (do (loop-do-do)) + (doing (loop-do-do)) + (return (loop-do-return)) + (collect (loop-list-collection list)) + (collecting (loop-list-collection list)) + (append (loop-list-collection append)) + (appending (loop-list-collection append)) + (nconc (loop-list-collection nconc)) + (nconcing (loop-list-collection nconc)) + (count (loop-sum-collection count ,*loop-real-data-type* fixnum)) + (counting (loop-sum-collection count ,*loop-real-data-type* fixnum)) + (sum (loop-sum-collection sum number number)) + (summing (loop-sum-collection sum number number)) + (maximize (loop-maxmin-collection max)) + (minimize (loop-maxmin-collection min)) + (maximizing (loop-maxmin-collection max)) + (minimizing (loop-maxmin-collection min)) + (always (loop-do-always t nil)) ; Normal, do always + (never (loop-do-always t t)) ; Negate the test on always. + (thereis (loop-do-thereis t)) + (while (loop-do-while nil :while)) ; Normal, do while + (until (loop-do-while t :until)) ; Negate the test on while + (when (loop-do-if when nil)) ; Normal, do when + (if (loop-do-if if nil)) ; synonymous + (unless (loop-do-if unless t)) ; Negate the test on when + (with (loop-do-with)) + (repeat (loop-do-repeat))) + :for-keywords '((= (loop-ansi-for-equals)) + (across (loop-for-across)) + (in (loop-for-in)) + (on (loop-for-on)) + (from (loop-for-arithmetic :from)) + (downfrom (loop-for-arithmetic :downfrom)) + (upfrom (loop-for-arithmetic :upfrom)) + (below (loop-for-arithmetic :below)) + (above (loop-for-arithmetic :above)) + (to (loop-for-arithmetic :to)) + (upto (loop-for-arithmetic :upto)) + (downto (loop-for-arithmetic :downto)) + (by (loop-for-arithmetic :by)) + (being (loop-for-being))) + :iteration-keywords '((for (loop-do-for)) + (as (loop-do-for))) + :type-symbols '(array atom bignum bit bit-vector character compiled-function + complex cons double-float fixnum float + function hash-table integer keyword list long-float + nil null number package pathname random-state + ratio rational readtable sequence short-float + simple-array simple-bit-vector simple-string + simple-vector single-float standard-char + stream string base-char + symbol t vector) + :type-keywords nil + :ansi (if extended-p :extended t)))) + (add-loop-path '(hash-key hash-keys) 'loop-hash-table-iteration-path w + :preposition-groups '((:of :in)) + :inclusive-permitted nil + :user-data '(:which hash-key)) + (add-loop-path '(hash-value hash-values) 'loop-hash-table-iteration-path w + :preposition-groups '((:of :in)) + :inclusive-permitted nil + :user-data '(:which hash-value)) + (add-loop-path '(symbol symbols) 'loop-package-symbols-iteration-path w + :preposition-groups '((:of :in)) + :inclusive-permitted nil + :user-data '(:symbol-types (:internal :external :inherited))) + (add-loop-path '(external-symbol external-symbols) 'loop-package-symbols-iteration-path w + :preposition-groups '((:of :in)) + :inclusive-permitted nil + :user-data '(:symbol-types (:external))) + (add-loop-path '(present-symbol present-symbols) 'loop-package-symbols-iteration-path w + :preposition-groups '((:of :in)) + :inclusive-permitted nil + :user-data '(:symbol-types (:internal :external))) + w)) + + + (defparameter *loop-ansi-universe* + (make-ansi-loop-universe nil)) + + (defun loop-standard-expansion (keywords-and-forms environment universe) + (if (and keywords-and-forms (symbolp (car keywords-and-forms))) + (loop-translate keywords-and-forms environment universe) + (let ((tag (gensym))) + `(block nil (tagbody ,tag (progn ,@keywords-and-forms) (go ,tag)))))) + + ) ;; eval-when + + +;;;INTERFACE: ANSI +(defmacro loop (&environment env &rest keywords-and-forms) + #+Genera (declare (compiler:do-not-record-macroexpansions) + (zwei:indentation . zwei:indent-loop)) + (loop-standard-expansion keywords-and-forms env *loop-ansi-universe*)) + +#+allegro +(defun excl::complex-loop-expander (body env) + (loop-standard-expansion body env *loop-ansi-universe*)) + +;; Replace the CL::LOOP macro with this macro for use with CLSQL +;; LOOP extensions +#+clisp +(eval-when (:compile-toplevel :load-toplevel :execute) + (shadowing-import '(loop loop-finish) (find-package "COMMON-LISP")) + (setf (ext:package-lock (find-package "COMMON-LISP")) t)) + diff --git a/sql/base-classes.lisp b/sql/base-classes.lisp new file mode 100644 index 0000000..344e11c --- /dev/null +++ b/sql/base-classes.lisp @@ -0,0 +1,57 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: base-classes.lisp +;;;; Purpose: Base classes for high-level SQL interface +;;;; Programmers: Kevin M. Rosenberg based on +;;;; original code by Pierre R. Mai +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + + +(defclass database () + ((name :initform nil :initarg :name :reader database-name) + (connection-spec :initform nil :initarg :connection-spec + :reader connection-spec + :documentation "Required to use connection pool.") + (database-type :initarg :database-type :initform :unknown + :reader database-type) + (encoding :initarg :encoding :initform nil + :documentation "External format character encoding.") + (state :initform :closed :reader database-state) + (autocommit :initform t :accessor database-autocommit) + (command-recording-stream :accessor command-recording-stream :initform nil) + (result-recording-stream :accessor result-recording-stream :initform nil) + (record-caches :accessor record-caches :initform nil) + (view-classes :accessor database-view-classes :initform nil) + (transaction-level :initform 0 :accessor transaction-level) + (transaction :initform nil :accessor transaction) + (conn-pool :initform nil :initarg :conn-pool :accessor conn-pool) + (attribute-cache :initform (make-hash-table :size 100 :test 'equal) + :accessor attribute-cache + :documentation "Internal cache of table attributes. It is keyed by table-name. Values +are a list of ACTION specified for table and any cached value of list-attributes-types.")) + (:documentation + "This class is the supertype of all databases handled by CLSQL.")) + +(defmethod print-object ((object database) stream) + (print-unreadable-object (object stream :type t :identity t) + (format stream "~A ~A" + (if (slot-boundp object 'name) + (database-name object) + "") + (database-state object))) + object) + +(setf (documentation 'database-name 'function) + "Returns the name of a database.") diff --git a/sql/cmucl-compat.lisp b/sql/cmucl-compat.lisp new file mode 100644 index 0000000..9ea2561 --- /dev/null +++ b/sql/cmucl-compat.lisp @@ -0,0 +1,101 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: cmucl-compat.lisp +;;;; Purpose: Compatiblity library for CMUCL functions +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:cmucl-compat + (:use #:common-lisp) + (:export + #:shrink-vector + #:make-sequence-of-type + #:result-type-or-lose + #:required-argument + )) +(in-package #:cmucl-compat) + +#+(or cmu scl) +(defmacro required-argument () + `(ext:required-argument)) + +#-(or cmu scl) +(defun required-argument () + (error "~&A required keyword argument was not supplied")) + +#+(or cmu scl) +(defmacro shrink-vector (vec len) + `(lisp::shrink-vector ,vec ,len)) + +#+sbcl +(defmacro shrink-vector (vec len) + `(sb-kernel::shrink-vector ,vec ,len)) + +#-(or cmu sbcl scl) +(defmacro shrink-vector (vec len) + "Shrinks a vector. Optimized if vector has a fill pointer. +Needs to be a macro to overwrite value of VEC." + (let ((new-vec (gensym))) + `(cond + ((adjustable-array-p ,vec) + (adjust-array ,vec ,len)) + ((typep ,vec 'simple-array) + (let ((,new-vec (make-array ,len :element-type + (array-element-type ,vec)))) + (check-type ,len fixnum) + (locally (declare (optimize (speed 3) (safety 0) (space 0)) ) + (dotimes (i ,len) + (declare (fixnum i)) + (setf (aref ,new-vec i) (aref ,vec i)))) + (setq ,vec ,new-vec))) + ((typep ,vec 'vector) + (setf (fill-pointer ,vec) ,len) + ,vec) + (t + (error "Unable to shrink vector ~S which is type-of ~S" ,vec (type-of ,vec))) + ))) + + +#-(or cmu scl) +(defun make-sequence-of-type (type length) + "Returns a sequence of the given TYPE and LENGTH." + (make-sequence type length)) + +#+(or cmu scl) +(if (fboundp 'lisp::make-sequence-of-type) + (defun make-sequence-of-type (type len) + (lisp::make-sequence-of-type type len)) + (defun make-sequence-of-type (type len) + (common-lisp::make-sequence-of-type type len))) + +#-(or cmu scl) +(defun result-type-or-lose (type nil-ok) + (unless (or type nil-ok) + (error "NIL output type invalid for this sequence function")) + (case type + ((list cons) + 'list) + ((string simple-string base-string simple-base-string) + 'string) + (simple-vector + 'simple-vector) + (vector + 'vector) + (t + (error "~S is a bad type specifier for sequence functions." type)) + )) + +#+(or cmu scl) +(defun result-type-or-lose (type nil-ok) + (lisp::result-type-or-lose type nil-ok)) diff --git a/sql/command-object.lisp b/sql/command-object.lisp new file mode 100644 index 0000000..6af4bc8 --- /dev/null +++ b/sql/command-object.lisp @@ -0,0 +1,73 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: postgresql-socket-sql.sql +;;;; Purpose: High-level PostgreSQL interface using socket +;;;; Authors: Russ Tyndall (at Acceleration.net) based on original code by +;;;; Kevin M. Rosenberg based on original code by Pierre R. Mai +;;;; Created: Sep 2009 +;;;; +;;;; +;;;; $Id$ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2007 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defclass command-object () + ((expression :accessor expression :initarg :expression :initform nil + :documentation "query that refers to parameters using \"$1\", \"$2\", \"$n\". + These match positions in the parameters list.") + (parameters :accessor parameters :initarg :parameters :initform nil + :documentation "list of parameters") + (prepared-name :accessor prepared-name :initarg :prepared-name :initform "" + :documentation "If we want this to be a prepared statement, give it a name + to identify it to this session") + (has-been-prepared :accessor has-been-prepared :initarg :has-been-prepared :initform nil + :documentation "Have we already prepared this command object?") + )) + + +(defgeneric prepare-sql-parameter (sql-parameter) + (:documentation "This method is responsible for formatting parameters + as the database expects them (eg: :false is nil, nil is :null, dates are iso8601 strings)") + (:method (sql-parameter) + (typecase sql-parameter + (null :null) + (symbol + (if (member sql-parameter (list :false :F)) + nil + (princ-to-string sql-parameter))) + (clsql-sys:date (format-date nil sql-parameter :format :iso8601)) + (clsql-sys:wall-time (format-time nil sql-parameter :format :iso8601)) + (t sql-parameter)))) + +(defmethod initialize-instance :after ((o command-object) &key &allow-other-keys ) + ;; Inits parameter value coersion + (setf (parameters o) (parameters o))) + +(defmethod (setf parameters) (new (o command-object)) + " This causes the semantics to match cl-sql instead of cl-postgresql + " + (setf (slot-value o 'parameters) + (loop for p in new collecting (prepare-sql-parameter p)))) + +(defun reset-command-object (co) + "Resets the command object to have no name and to be unprepared + (This is useful if you want to run a command against a second database)" + (setf (prepared-name co) "" + (has-been-prepared co) nil)) + +(defun command-object (expression &optional parameters (prepared-name "")) + (make-instance 'command-object + :expression expression + :parameters parameters + :prepared-name prepared-name)) diff --git a/sql/conditions.lisp b/sql/conditions.lisp new file mode 100644 index 0000000..e198052 --- /dev/null +++ b/sql/conditions.lisp @@ -0,0 +1,170 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: conditions.lisp +;;;; Purpose: Error conditions for CLSQL +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defvar *backend-warning-behavior* :warn + "Action to perform on warning messages from backend. Default is +to :warn. May also be set to :error to signal an error +or :ignore/nil to silently ignore the warning.") + +;;; CommonSQL-compatible conditions + +(define-condition sql-condition () + ()) + +(define-condition sql-error (simple-error sql-condition) + ()) + +(define-condition sql-database-error (sql-error) + ((error-id :initarg :error-id + :initform nil + :reader sql-error-error-id) + (secondary-error-id :initarg :secondary-error-id + :initform nil + :reader sql-error-secondary-error-id) + (database-message :initarg :message + :initform nil + :reader sql-error-database-message) + (database :initarg :database + :initform nil + :reader sql-error-database)) + (:report (lambda (c stream) + (format stream "A database error occurred~@[ on database ~A~]: ~A / ~A~% ~A" + (sql-error-database c) + (sql-error-error-id c) + (sql-error-secondary-error-id c) + (sql-error-database-message c)))) + (:documentation "Used to signal an error in a CLSQL database interface.")) + +(define-condition sql-connection-error (sql-database-error) + ((database-type :initarg :database-type :initform nil + :reader sql-error-database-type) + (connection-spec :initarg :connection-spec :initform nil + :reader sql-error-connection-spec)) + (:report (lambda (c stream) + (format stream "While trying to connect to database ~A~% using database-type ~A:~% Error ~D / ~A~% has occurred." + (when (and (sql-error-connection-spec c) + (sql-error-database-type c)) + (database-name-from-spec + (sql-error-connection-spec c) + (sql-error-database-type c))) + (sql-error-database-type c) + (sql-error-error-id c) + (sql-error-database-message c)))) + (:documentation "Used to signal an error in connecting to a database.")) + +(define-condition sql-database-data-error (sql-database-error) + ((expression :initarg :expression :initarg nil + :reader sql-error-expression)) + (:report (lambda (c stream) + (format stream "While accessing database ~A~% with expression ~S:~% Error ~D / ~A~% has occurred." + (sql-error-database c) + (sql-error-expression c) + (sql-error-error-id c) + (sql-error-database-message c)))) + (:documentation "Used to signal an error with the SQL data + passed to a database.")) + +(define-condition sql-temporary-error (sql-database-error) + () + (:documentation "Used to signal an error when the database +cannot currently process a valid interaction because, for +example, it is still executing another command possibly issued by +another user.")) + +(define-condition sql-timeout-error (sql-connection-error) + () + (:documentation "Used to signal an error when the database +times out while processing some operation.")) + +(define-condition sql-fatal-error (sql-connection-error) + () + (:documentation "Used to signal an error when the database +connection is no longer usable.")) + +(define-condition sql-user-error (sql-error) + ((message :initarg :message + :initform "Unspecified error" + :reader sql-user-error-message)) + (:report (lambda (c stream) + (format stream "A CLSQL lisp code error occurred: ~A " + (sql-user-error-message c)))) + (:documentation "Used to signal lisp errors inside CLSQL.")) + + + +;; Signal conditions + +(defun signal-closed-database-error (database) + (error 'sql-fatal-error + :database database + :connection-spec (when database (connection-spec database)) + :database-type (when database (database-type database)) + :message "Database is closed.")) + +(defun signal-no-database-error (database) + (error 'sql-database-error + :database database + :message (format nil "~A is not a database." database))) + + +;;; CLSQL Extensions + +(define-condition sql-warning (warning sql-condition) + ((message :initarg :message :initform nil :reader sql-warning-message)) + (:report (lambda (c stream) + (format stream "~A" (sql-warning-message c))))) + +(define-condition sql-database-warning (sql-warning) + ((database :initarg :database :reader sql-warning-database)) + (:report (lambda (c stream) + (format stream + "While accessing database ~A~% Warning: ~A~% has occurred." + (sql-warning-database c) + (sql-warning-message c))))) + +(define-condition database-too-strange (sql-user-error) + () + (:documentation "Used to signal cases where CLSQL is going to fail at + mapping your database correctly")) + +(defun signal-database-too-strange (message) + (error 'database-too-strange :message message)) + + +(define-condition sql-value-conversion-error (error) + ((expected-type :accessor expected-type :initarg :expected-type :initform nil) + (value :accessor value :initarg :value :initform nil) + (database :accessor database :initarg :database :initform nil))) + +(defun error-converting-value (val type &optional (database *default-database*)) + (restart-case + (error (make-condition + 'sql-value-conversion-error + :expected-type type :value val :database database)) + (continue () + :report "Continue using the unconverted value" + (values val t)) + (use-value (new-val) + :report "Use a different value instead of this failed conversion" + (values new-val t) + ))) + +(defun maybe-error-converting-value + (new val type &optional (database *default-database*)) + (if (typep new type) + new + (error-converting-value + val type database))) diff --git a/sql/database.lisp b/sql/database.lisp new file mode 100644 index 0000000..299eadc --- /dev/null +++ b/sql/database.lisp @@ -0,0 +1,363 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Base database functions +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defvar *default-encoding* + (or #+sbcl sb-impl::*default-external-format* + :utf-8)) + +(defvar *connect-if-exists* :error + "Default value for the if-exists keyword argument in calls to +CONNECT. Meaningful values are :new, :warn-new, :error, :warn-old +and :old.") + +;;TODO: this variable appears to be global, not thread specific and is +;; not protected when modifying the list. +(defvar *connected-databases* nil + "List of active database objects.") + +(defun connected-databases () + "Returns the list of active database objects." + *connected-databases*) + +(defvar *default-database* nil + "Specifies the default database to be used.") + +(defun is-database-open (database) + (eql (database-state database) :open)) + +(defun find-database (database &key (errorp t) (db-type nil)) + "Returns the connected databases of type DB-TYPE whose names +match the string DATABASE. If DATABASE is a database object, it +is returned. If DB-TYPE is nil all databases matching the string +DATABASE are considered. If no matching databases are found and +ERRORP is nil then nil is returned. If ERRORP is nil and one or +more matching databases are found, then the most recently +connected database is returned as a first value and the number of +matching databases is returned as a second value. If no, or more +than one, matching databases are found and ERRORP is true, an +error is signalled." + (etypecase database + (database + (values database 1)) + (string + (let* ((matches (remove-if + #'(lambda (db) + (not (and (string= (database-name db) database) + (if db-type + (equal (database-type db) db-type) + t)))) + (connected-databases))) + (count (length matches))) + (if (or (not errorp) (= count 1)) + (values (car matches) count) + (cerror "Return nil." + 'sql-database-error + :message + (format nil "There exists ~A database called ~A." + (if (zerop count) "no" "more than one") + database))))) + (null + (error "A database must be specified rather than NIL.")))) + + +(defun connect (connection-spec + &key (if-exists *connect-if-exists*) + (make-default t) + (pool nil) + (database-type *default-database-type*) + (encoding *default-encoding*)) + "Connects to a database of the supplied DATABASE-TYPE which +defaults to *DEFAULT-DATABASE-TYPE*, using the type-specific +connection specification CONNECTION-SPEC. The value of IF-EXISTS, +which defaults to *CONNECT-IF-EXISTS*, determines what happens if +a connection to the database specified by CONNECTION-SPEC is +already established. A value of :new means create a new +connection. A value of :warn-new means warn the user and create +a new connect. A value of :warn-old means warn the user and use +the old connection. A value of :error means fail, notifying the +user. A value of :old means return the old connection. +MAKE-DEFAULT is t by default which means that *DEFAULT-DATABASE* +is set to the new connection, otherwise *DEFAULT-DATABASE* is not +changed. If POOL is t the connection will be taken from the +general pool, if POOL is a CONN-POOL object the connection will +be taken from this pool." + + (unless database-type + (error 'sql-database-error :message "Must specify a database-type.")) + + (when (stringp connection-spec) + (setq connection-spec (string-to-list-connection-spec connection-spec))) + + (unless (member database-type *loaded-database-types*) + (asdf:operate 'asdf:load-op (ensure-keyword + (concatenate 'string + (symbol-name '#:clsql-) + (symbol-name database-type))) + :verbose nil)) + + (if pool + (let ((conn (acquire-from-pool connection-spec database-type pool encoding))) + (when make-default (setq *default-database* conn)) + conn) + (let* ((db-name (database-name-from-spec connection-spec database-type)) + (old-db (unless (eq if-exists :new) + (find-database db-name :db-type database-type + :errorp nil))) + (result nil)) + (if old-db + (ecase if-exists + (:warn-new + (setq result + (database-connect connection-spec database-type)) + (warn 'sql-warning + :message + (format nil + "Created new connection ~A to database ~A~%, although there is an existing connection (~A)." + result (database-name result) old-db))) + (:error + (restart-case + (error 'sql-connection-error + :message + (format nil "There is an existing connection ~A to database ~A." + old-db + (database-name old-db))) + (create-new () + :report "Create a new connection." + (setq result + (database-connect connection-spec database-type))) + (use-old () + :report "Use the existing connection." + (setq result old-db)))) + (:warn-old + (setq result old-db) + (warn 'sql-warning + :message + (format nil + "Using existing connection ~A to database ~A." + old-db + (database-name old-db)))) + (:old + (setq result old-db))) + (setq result + (database-connect connection-spec database-type))) + (when result + (setf (slot-value result 'state) :open) + (pushnew result *connected-databases*) + (when make-default (setq *default-database* result)) + (setf (encoding result) encoding) + result)))) + + +(defun disconnect (&key (database *default-database*) (error nil)) + + "Closes the connection to DATABASE and resets +*DEFAULT-DATABASE* if that database was disconnected. If DATABASE +is a database instance, this object is closed. If DATABASE is a +string, then a connected database whose name matches DATABASE is +sought in the list of connected databases. If no matching +database is found and ERROR and DATABASE are both non-nil an +error is signaled, otherwise nil is returned. If the database is +from a pool it will be released to this pool." + (let ((database (find-database database :errorp (and database error)))) + (when database + (if (conn-pool database) + (with-process-lock ((conn-pool-lock (conn-pool database)) "Delete from pool") + (when (release-to-pool database) + (setf *connected-databases* (delete database *connected-databases*)) + (when (eq database *default-database*) + (setf *default-database* (car *connected-databases*))) + t)) + (when (database-disconnect database) + ;;TODO: RACE COND: 2 threads disconnecting could stomp on *connected-databases* + (setf *connected-databases* (delete database *connected-databases*)) + (when (eq database *default-database*) + (setf *default-database* (car *connected-databases*))) + (setf (slot-value database 'state) :closed) + t))))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defmacro check-connection-spec (connection-spec database-type template) + "Check the connection specification against the provided template, +and signal an sql-user-error if they don't match. This function +is called by database backends." + `(handler-case + (destructuring-bind ,template ,connection-spec + (declare (ignore ,@(remove-if + (lambda (x) (member x '(&key &rest &optional))) + template))) + t) + (error () + (error 'sql-user-error + :message + (format nil + "The connection specification ~A~%is invalid for database type ~A.~%The connection specification must conform to ~A" + ,connection-spec + ,database-type + (quote ,template)))))) + +(defun reconnect (&key (database *default-database*) (error nil) (force t)) + "Reconnects DATABASE which defaults to *DEFAULT-DATABASE* to +the underlying database management system. On success, t is +returned and the variable *DEFAULT-DATABASE* is set to the newly +reconnected database. If DATABASE is a database instance, this +object is closed. If DATABASE is a string, then a connected +database whose name matches DATABASE is sought in the list of +connected databases. If no matching database is found and ERROR +and DATABASE are both non-nil an error is signaled, otherwise nil +is returned. When the current database connection cannot be +closed, if FORCE is non-nil, as it is by default, the connection +is closed and errors are suppressed. If force is nil and the +database connection cannot be closed, an error is signalled." + (let ((db (etypecase database + (database database) + ((or string list) + (let ((db (find-database database :errorp nil))) + (when (null db) + (if (and database error) + (error 'sql-connection-error + :message + (format nil "Unable to find database with connection-spec ~A." database)) + (return-from reconnect nil))) + db))))) + + (when (is-database-open db) + (if force + (ignore-errors (disconnect :database db)) + (disconnect :database db :error nil))) + + (connect (connection-spec db) :encoding (encoding db)))) + + +(defun status (&optional full) + "Prints information about the currently connected databases to +*STANDARD-OUTPUT*. The argument FULL is nil by default and a +value of t means that more detailed information about each +database is printed." + (flet ((get-data () + (let ((data '())) + (dolist (db (connected-databases) data) + (push + (append + (list (if (equal db *default-database*) "*" "") + (database-name db) + (string-downcase (string (database-type db))) + (cond ((and (command-recording-stream db) + (result-recording-stream db)) + "Both") + ((command-recording-stream db) "Commands") + ((result-recording-stream db) "Results") + (t "nil"))) + (when full + (list + (if (conn-pool db) "t" "nil") + (format nil "~A" (length (database-list-tables db))) + (format nil "~A" (length (database-list-views db)))))) + data)))) + (compute-sizes (data) + (mapcar #'(lambda (x) (apply #'max (mapcar #'length x))) + (apply #'mapcar (cons #'list data)))) + (print-separator (size) + (format t "~&~A" (make-string size :initial-element #\-)))) + (format t "~&CLSQL STATUS: ~A~%" (iso-timestring (get-time))) + (let ((data (get-data))) + (when data + (let* ((titles (if full + (list "" "DATABASE" "TYPE" "RECORDING" "POOLED" + "TABLES" "VIEWS") + (list "" "DATABASE" "TYPE" "RECORDING"))) + (sizes (compute-sizes (cons titles data))) + (total-size (+ (apply #'+ sizes) (* 2 (1- (length titles))))) + (control-string (format nil "~~&~~{~{~~~AA ~}~~}" sizes))) + (print-separator total-size) + (format t control-string titles) + (print-separator total-size) + (dolist (d data) (format t control-string d)) + (print-separator total-size)))) + (values))) + +(defun create-database (connection-spec &key (database-type *default-database-type*)) + "This function creates a database in the database system specified +by DATABASE-TYPE." + (when (stringp connection-spec) + (setq connection-spec (string-to-list-connection-spec connection-spec))) + (database-create connection-spec database-type)) + +(defun probe-database (connection-spec &key (database-type *default-database-type*)) + "This function tests for the existence of a database in the database +system specified by DATABASE-TYPE." + (when (stringp connection-spec) + (setq connection-spec (string-to-list-connection-spec connection-spec))) + (database-probe connection-spec database-type)) + +(defun destroy-database (connection-spec &key (database-type *default-database-type*)) + "This function destroys a database in the database system specified +by DATABASE-TYPE." + (when (stringp connection-spec) + (setq connection-spec (string-to-list-connection-spec connection-spec))) + (database-destroy connection-spec database-type)) + +(defun list-databases (connection-spec &key (database-type *default-database-type*)) + "This function returns a list of databases existing in the database +system specified by DATABASE-TYPE." + (when (stringp connection-spec) + (setq connection-spec (string-to-list-connection-spec connection-spec))) + (database-list connection-spec database-type)) + +(defun encoding (db) + (or (when (typep db 'database) + (slot-value db 'encoding)) + *default-encoding*)) + +(defun (setf encoding) (encoding db) + (when (typep db 'database) + (setf (slot-value db 'encoding) encoding) + (when (eql (slot-value db 'state) :open) + (case (database-type db) + ;; FIXME: If database object is open then + ;; send command to SQL engine specifying the character + ;; encoding for the database + (:mysql + ) + ((:postgresql :postgresql-socket) + ))))) + +(defmacro with-database ((db-var connection-spec + &key make-default pool + (if-exists *connect-if-exists*) + (database-type *default-database-type*) + (encoding nil)) + &body body) + "Evaluate the body in an environment, where DB-VAR is bound to the +database connection given by CONNECTION-SPEC and CONNECT-ARGS. The +connection is automatically closed or released to the pool on exit +from the body. MAKE-DEFAULT has a default value of NIL." + `(let ((,db-var (connect ,connection-spec + :database-type ,database-type + :if-exists ,if-exists + :pool ,pool + :make-default ,make-default + :encoding ,encoding))) + (unwind-protect + (let ((,db-var ,db-var)) + (progn ,@body)) + (disconnect :database ,db-var)))) + +(defmacro with-default-database ((database) &rest body) + "Perform BODY with DATABASE bound as *default-database*." + `(progv '(*default-database*) + (list ,database) + ,@body)) diff --git a/sql/db-interface.lisp b/sql/db-interface.lisp new file mode 100644 index 0000000..3454a84 --- /dev/null +++ b/sql/db-interface.lisp @@ -0,0 +1,500 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: db-interface.lisp +;;;; Purpose: Generic function definitions for DB interfaces +;;;; Programmers: Kevin M. Rosenberg based on +;;;; Original code by Pierre R. Mai. Additions from +;;;; onShoreD to support UncommonSQL front-end +;;;; Date Started: Feb 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai, and onShoreD +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defgeneric database-type-load-foreign (database-type) + (:documentation + "The internal generic implementation of reload-database-types.")) + +(defgeneric database-type-library-loaded (database-type) + (:documentation + "The internal generic implementation for checking if +database type library loaded successfully.")) + +(defgeneric database-initialize-database-type (database-type) + (:documentation + "The internal generic implementation of initialize-database-type.")) + +(defgeneric database-name-from-spec (connection-spec database-type) + (:documentation + "Returns the name of the database that would be created if connect +was called with the connection-spec.")) + +(defgeneric database-connect (connection-spec database-type) + (:documentation "Internal generic implementation of connect.")) + +(defgeneric database-reconnect (database) + (:method ((database t)) + (signal-no-database-error database)) + (:documentation "Internal generic implementation of reconnect.")) + +(defgeneric database-disconnect (database) + (:method ((database t)) + (signal-no-database-error database)) + (:documentation "Internal generic implementation of disconnect.")) + +(defgeneric database-query (query-expression database result-types field-names) + (:method (query-expression (database t) result-types field-names) + (declare (ignore query-expression result-types field-names)) + (signal-no-database-error database)) + (:method (query-expression (database database) result-types field-names) + (declare (ignore query-expression result-types field-names)) + (warn "database-query not implemented for database type ~A." + (database-type database))) + (:documentation "Internal generic implementation of query.")) + + +(defgeneric database-execute-command (sql-expression database) + (:method (sql-expression (database t)) + (declare (ignore sql-expression)) + (signal-no-database-error database)) + (:method (sql-expression (database database)) + (declare (ignore sql-expression)) + (warn "database-execute-command not implemented for database type ~A." + (database-type database))) + (:documentation "Internal generic implementation of execute-command.")) + +;;; Mapping and iteration +(defgeneric database-query-result-set + (query-expression database &key full-set result-types) + (:method (query-expression (database t) &key full-set result-types) + (declare (ignore query-expression full-set result-types)) + (signal-no-database-error database) + (values nil nil nil)) + (:method (query-expression (database database) &key full-set result-types) + (declare (ignore query-expression full-set result-types)) + (warn "database-query-result-set not implemented for database type ~A." + (database-type database)) + (values nil nil nil)) + (:documentation + "Internal generic implementation of query mapping. Starts the +query specified by query-expression on the given database and returns +a result-set to be used with database-store-next-row and +database-dump-result-set to access the returned data. The second +value is the number of columns in the result-set, if there are any. +If full-set is true, the number of rows in the result-set is returned +as a third value, if this is possible (otherwise nil is returned for +the third value). This might have memory and resource usage +implications, since many databases will require the query to be +executed in full to answer this question. If the query produced no +results then nil is returned for all values that would have been +returned otherwise. If an error occurs during query execution, the +function should signal a sql-database-data-error.")) + +(defgeneric database-dump-result-set (result-set database) + (:method (result-set (database t)) + (declare (ignore result-set)) + (signal-no-database-error database)) + (:method (result-set (database database)) + (declare (ignore result-set)) + (warn "database-dump-result-set not implemented for database type ~A." + (database-type database))) + (:documentation "Dumps the received result-set.")) + +(defgeneric database-store-next-row (result-set database list) + (:method (result-set (database t) list) + (declare (ignore result-set list)) + (signal-no-database-error database)) + (:method (result-set (database database) list) + (declare (ignore result-set list)) + (warn "database-store-next-row not implemented for database type ~A." + (database-type database))) + (:documentation + "Returns t and stores the next row in the result set in list or +returns nil when result-set is finished.")) + +(defgeneric database-create (connection-spec type) + (:documentation + "Creates a database, returns T if successfull or signals an error.")) + +(defgeneric database-probe (connection-spec type) + (:method (spec type) + (declare (ignore spec)) + (warn "database-probe not support for database-type ~A." type)) + (:documentation + "Probes for the existence of a database, returns T if database found or NIL +if not found. May signal an error if unable to communicate with database server.")) + +(defgeneric database-list (connection-spec type) + (:method (spec type) + (declare (ignore spec)) + (warn "database-list not support for database-type ~A." type)) + (:documentation + "Lists all databases found for TYPE. May signal an error if unable to communicate with database server.")) + +(defgeneric database-truncate (database) + (:method ((database t)) + (signal-no-database-error database)) + (:documentation "Remove all data from database.")) + +(defgeneric database-destroy (connection-spec type) + (:documentation + "Destroys (drops) a database, returns T if successfull or signals an error +if unable to destory.")) + +(defgeneric database-create-sequence (name database) + (:documentation "Create a sequence in DATABASE.")) + +(defgeneric database-drop-sequence (name database) + (:documentation "Drop a sequence from DATABASE.")) + +(defgeneric database-sequence-next (name database) + (:documentation "Increment a sequence in DATABASE.")) + +(defgeneric database-list-sequences (database &key owner) + (:documentation "List all sequences in DATABASE.")) + +(defgeneric database-set-sequence-position (name position database) + (:documentation "Set the position of the sequence called NAME in DATABASE.")) + +(defgeneric database-sequence-last (name database) + (:documentation "Select the last value in sequence NAME in DATABASE.")) + +(defgeneric database-last-autoincrement-id (database table column) + (:documentation "Many databases have the notion of an auto-increment + id; i.e. a sequence implicitly on a table. This function should + return that ID." )) + + +(defgeneric database-start-transaction (database) + (:documentation "Start a transaction in DATABASE.") + (:method ((database t)) + (signal-no-database-error database))) + +(defgeneric database-commit-transaction (database) + (:documentation "Commit current transaction in DATABASE.") + (:method ((database t)) + (signal-no-database-error database))) + +(defgeneric database-abort-transaction (database) + (:documentation "Abort current transaction in DATABASE.") + (:method ((database t)) + (signal-no-database-error database))) + +(defgeneric database-get-type-specifier (type args database db-underlying-type) + (:documentation "Return the type SQL type specifier as a string, for +the given lisp type and parameters.")) + +(defgeneric database-list-tables (database &key owner) + (:documentation "List all tables in the given database") + (:method ((database database) &key owner) + (declare (ignore owner)) + (warn "database-list-tables not implemented for database type ~A." + (database-type database))) + (:method ((database t) &key owner) + (declare (ignore owner)) + (signal-no-database-error database))) + +(defgeneric database-list-tables-and-sequences (database &key owner) + (:documentation "List all tables in the given database, may include seqeneces") + (:method ((database t) &key owner) + (declare (ignore owner)) + (signal-no-database-error database)) + (:method ((database database) &key owner) + (database-list-tables database :owner owner))) + +(defgeneric database-list-views (database &key owner) + (:documentation "List all views in the DATABASE.") + (:method ((database database) &key owner) + (declare (ignore owner)) + (warn "database-list-views not implemented for database type ~A." + (database-type database))) + (:method ((database t) &key owner) + (declare (ignore owner)) + (signal-no-database-error database))) + +(defgeneric database-list-indexes (database &key owner) + (:documentation "List all indexes in the DATABASE.") + (:method ((database database) &key owner) + (declare (ignore owner)) + (warn "database-list-indexes not implemented for database type ~A." + (database-type database))) + (:method ((database t) &key owner) + (declare (ignore owner)) + (signal-no-database-error database))) + +(defgeneric database-list-table-indexes (table database &key owner) + (:documentation "List all indexes for a table in the DATABASE.") + (:method (table (database database) &key owner) + (declare (ignore table owner)) + (warn "database-list-table-indexes not implemented for database type ~A." + (database-type database))) + (:method (table (database t) &key owner) + (declare (ignore table owner)) + (signal-no-database-error database))) + +(defgeneric database-list-attributes (table database &key owner) + (:documentation "List all attributes in TABLE.") + (:method (table (database database) &key owner) + (declare (ignore table owner)) + (warn "database-list-attributes not implemented for database type ~A." + (database-type database))) + (:method (table (database t) &key owner) + (declare (ignore table owner)) + (signal-no-database-error database))) + +(defgeneric database-attribute-type (attribute table database &key owner) + (:documentation "Return the type of ATTRIBUTE in TABLE. Returns multiple values +of TYPE_NAME (keyword) PRECISION SCALE NULLABLE.") + (:method (attribute table (database database) &key owner) + (declare (ignore attribute table owner)) + (warn "database-list-attribute-type not implemented for database type ~A." + (database-type database))) + (:method (attribute table (database t) &key owner) + (declare (ignore attribute table owner)) + (signal-no-database-error database))) + +(defgeneric database-add-attribute (table attribute database) + (:documentation "Add the attribute to the table.") + (:method (table attribute (database database)) + (declare (ignore table attribute)) + (warn "database-add-attribute not implemented for database type ~A." + (database-type database))) + (:method (table attribute (database t)) + (declare (ignore table attribute)) + (signal-no-database-error database))) + +(defgeneric database-rename-attribute (table oldatt newname database) + (:documentation "Rename the attribute in the table to NEWNAME.") + (:method (table oldatt newname (database database)) + (declare (ignore table oldatt newname)) + (warn "database-rename-attribute not implemented for database type ~A." + (database-type database))) + (:method (table oldatt newname (database t)) + (declare (ignore table oldatt newname)) + (signal-no-database-error database))) + +(defgeneric oid (object) + (:documentation "Return the unique ID of a database object.")) + +;;; Database backend capabilities + +(defgeneric database-underlying-type (database) + (:method (database) + (database-type database)) + (:documentation "Returns the type of the underlying database. For ODBC, needs to query ODBC driver.")) + +(defgeneric db-type-use-column-on-drop-index? (db-type) + (:method (db-type) + (declare (ignore db-type)) + nil) + (:documentation "NIL [default] if database-type does not use column name on DROP INDEX.")) + +(defgeneric db-type-use-fully-qualified-column-on-drop-index? (db-type) + (:method (db-type) + (declare (ignore db-type)) + nil) + (:documentation "NIL [default] if database-type does not require fully qualified column name on DROP INDEX.")) + +(defgeneric db-type-has-views? (db-type) + (:method (db-type) + (declare (ignore db-type)) + ;; SQL92 has views + t) + (:documentation "T [default] if database-type supports views.")) + +(defgeneric db-type-has-bigint? (db-type) + (:method (db-type) + (declare (ignore db-type)) + ;; SQL92 has bigint + t) + (:documentation "T [default] if database-type supports bigint.")) + +(defgeneric db-type-default-case (db-type) + (:method (db-type) + (declare (ignore db-type)) + ;; By default, CommonSQL converts identifiers to UPPER case. + :upper) + (:documentation ":upper [default] if means identifiers mapped to UPPER case SQL like CommonSQL API. However, Postgresql maps identifiers to lower case, so PostgreSQL uses a value of :lower for this result.")) + +(defgeneric db-type-has-fancy-math? (db-type) + (:method (db-type) + (declare (ignore db-type)) + nil) + (:documentation "NIL [default] if database-type does not have fancy math.")) + +(defgeneric db-type-has-subqueries? (db-type) + (:method (db-type) + (declare (ignore db-type)) + t) + (:documentation "T [default] if database-type supports views.")) + +(defgeneric db-type-has-boolean-where? (db-type) + (:method (db-type) + (declare (ignore db-type)) + ;; SQL99 has boolean where + t) + (:documentation "T [default] if database-type supports boolean WHERE clause, such as 'WHERE MARRIED'.")) + +(defgeneric db-type-has-union? (db-type) + (:method (db-type) + (declare (ignore db-type)) + t) + (:documentation "T [default] if database-type supports boolean UNION.")) + +(defgeneric db-backend-has-create/destroy-db? (db-type) + (:method (db-type) + (declare (ignore db-type)) + t) + (:documentation "T [default] if backend can destroy and create databases.")) + +(defgeneric db-type-transaction-capable? (db database) + (:method (db database) + (declare (ignore db database)) + t) + (:documentation "T [default] if database can supports transactions.")) + +(defgeneric db-type-has-prepared-stmt? (db-type) + (:method ((db-type t)) + nil) + (:documentation "T if database backend supports prepared statements.")) + +(defgeneric db-type-has-intersect? (db-type) + (:method (db-type) + (declare (ignore db-type)) + t) + (:documentation "T [default] if database-type supports INTERSECT.")) + +(defgeneric db-type-has-except? (db-type) + (:method (db-type) + (declare (ignore db-type)) + t) + (:documentation "T [default] if database-type supports EXCEPT.")) + +(defgeneric db-type-has-auto-increment? (db-type) + (:method (db-type) + (declare (ignore db-type)) + nil) + (:documentation "NIL [default] if database-type supports auto-incrementing columns.")) + +;;; Large objects support (Marc Battyani) + +(defgeneric database-create-large-object (database) + (:documentation "Creates a new large object in the database and returns the object identifier")) + +(defgeneric database-write-large-object (object-id data database) + (:documentation "Writes data to the large object")) + +(defgeneric database-read-large-object (object-id database) + (:documentation "Reads the large object content")) + +(defgeneric database-delete-large-object (object-id database) + (:documentation "Deletes the large object in the database")) + +;; Prepared statements + +(defgeneric database-prepare (stmt types database result-types field-names) + (:method (stmt types (database t) result-types field-names) + (declare (ignore stmt types result-types field-names)) + (signal-no-database-error database)) + (:method (stmt types (database database) result-types field-names) + (declare (ignore stmt types result-types field-names)) + (error 'sql-database-error + :message + (format nil "DATABASE-PREPARE not implemented for ~S" database))) + (:documentation "Prepare a statement for later execution.")) + +(defgeneric database-bind-parameter (prepared-stmt position value) + (:method ((pstmt t) position value) + (declare (ignore position value)) + (error 'sql-database-error + :message + (format nil "database-bind-paremeter not implemented for ~S" pstmt))) + (:documentation "Bind a parameter for a prepared statement.")) + +(defgeneric database-run-prepared (prepared-stmt) + (:method ((pstmt t)) + (error 'sql-database-error + :message (format nil "database-run-prepared not specialized for ~S" pstmt))) + (:documentation "Execute a prepared statement.")) + +(defgeneric database-free-prepared (prepared-stmt) + (:method ((pstmt t)) + ;; nothing to do by default + nil) + (:documentation "Free the resources of a prepared statement.")) + +(defgeneric database-acquire-from-conn-pool (database) + (:documentation "Acquire a database connection from the pool. This +is a chance to test the connection for validity before returning it to +the user. If this function returns NIL or throws an error that +database connection is considered bad and we make a new one. + +Database objects have a chance to specialize, otherwise the default +method uses the database-underlying-type and tries to do something +appropriate.")) + +(defgeneric database-release-to-conn-pool (database) + (:documentation "Chance for the database to cleanup before it is + returned to the connection pool.")) + +;; Checks for closed database + +(defmethod database-disconnect :before ((database database)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-query :before (query-expression (database database) + result-set field-names) + (declare (ignore query-expression result-set field-names)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-execute-command :before (sql-expression (database database)) + (declare (ignore sql-expression)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-query-result-set :before (expr (database database) + &key full-set result-types) + (declare (ignore expr full-set result-types)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-dump-result-set :before (result-set (database database)) + (declare (ignore result-set)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-store-next-row :before (result-set (database database) list) + (declare (ignore result-set list)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-commit-transaction :before ((database database)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-start-transaction :before ((database database)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defmethod database-abort-transaction :before ((database database)) + (unless (is-database-open database) + (signal-closed-database-error database))) + +(defvar *foreign-library-search-paths* nil + "A list of pathnames denoting directories where CLSQL will look +for foreign libraries \(in addition to the default places).") + +(defun push-library-path (path) + "Adds the pathspec PATH \(which should denote a directory) to +the list *FOREIGN-LIBRARY-SEARCH-PATHS*." + (pushnew path *foreign-library-search-paths* :test #'equal)) diff --git a/sql/decimals.lisp b/sql/decimals.lisp new file mode 100644 index 0000000..b8df6fc --- /dev/null +++ b/sql/decimals.lisp @@ -0,0 +1,419 @@ +;;; DECIMALS +;; +;; A decimal number parser and formatting package for Common Lisp. +;; +;; Author: Teemu Likonen +;; +;; License: Public domain +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +(defpackage #:decimals + (:use #:cl) + (:export #:round-half-away-from-zero + #:format-decimal-number + #:parse-decimal-number + #:decimal-parse-error + #:define-decimal-formatter)) + +(in-package #:decimals) + + +(defun round-half-away-from-zero (number &optional (divisor 1)) + + "Divide _number_ by _divisor_ and round the result to the nearest integer. +If the result is half-way between two integers round away from zero. Two +values are returned: quotient and remainder. + +This is similar to `cl:round` function except that `cl:round` rounds to +an even integer when number is exactly between two integers. Examples: + + (round-half-away-from-zero 3/2) => 2, -1/2 + (round 3/2) => 2, -1/2 + + (round-half-away-from-zero 5/2) => 3, -1/2 + (round 5/2) => 2, 1/2" + + (if (zerop number) + (values 0 0) + (let ((quotient (if (plusp number) + (floor (+ (/ number divisor) 1/2)) + (ceiling (- (/ number divisor) 1/2))))) + (values quotient (- number (* quotient divisor)))))) + + +(defun divide-into-groups (string &key (separator #\Space) (from-end nil) + (group-digits 3)) + + (assert (and (integerp group-digits) + (plusp group-digits)) + (group-digits) + "The GROUP-DIGITS argument must be a positive integer") + + (setf separator (princ-to-string separator)) + + (if (zerop (length separator)) + string + (flet ((make-groups (string separator) + (loop :with length := (length string) + :with result := (make-array length :element-type 'character + :fill-pointer 0 :adjustable t) + :for c :across string + :for i :upfrom 1 + :do (vector-push-extend c result) + :if (and (zerop (rem i group-digits)) + (< i length)) + :do (loop :for c :across separator + :do (vector-push-extend c result)) + :finally (return result)))) + + (if from-end + (nreverse (make-groups (reverse string) (reverse separator))) + (make-groups string separator))))) + + +(defun decimal-round-split (number &key + (round-magnitude 0) + (rounder #'round-half-away-from-zero) + (positive-sign #\+) + (negative-sign #\-) + (zero-sign nil)) + + (assert (integerp round-magnitude) (round-magnitude) + "ROUND-MAGNITUDE argument must be an integer.") + + (when (floatp number) + (setf number (rational number))) + + (let ((divisor (expt 10 round-magnitude))) + (setf number (* divisor (funcall rounder number divisor)))) + + (let ((sign (cond ((plusp number) (or positive-sign "")) + ((minusp number) (or negative-sign "")) + (t (or zero-sign ""))))) + + (multiple-value-bind (integer fractional) + (truncate (abs number)) + (let ((fractional-string + (with-output-to-string (out) + (loop :with next := fractional + :with remainder + :repeat (abs round-magnitude) + :until (zerop next) + :do + (setf (values next remainder) (truncate (* next 10))) + (princ next out) + (setf next remainder))))) + (list (princ-to-string sign) + (princ-to-string integer) + fractional-string))))) + + +(defun string-align (string width &key (side :left) (char #\Space)) + (if (>= (length string) width) + string + (let ((result (make-string width :initial-element char))) + (ecase side + (:left (replace result string)) + (:right (replace result string + :start1 (- width (length string)))))))) + + +(defun format-decimal-number (number &key + (round-magnitude 0) + (rounder #'round-half-away-from-zero) + (decimal-separator #\.) + (integer-group-separator nil) + (integer-group-digits 3) + (integer-minimum-width 0) + (integer-pad-char #\Space) + (fractional-group-separator nil) + (fractional-group-digits 3) + (fractional-minimum-width 0) + (fractional-pad-char #\Space) + (show-trailing-zeros nil) + (positive-sign nil) + (negative-sign #\-) + (zero-sign nil)) + + "Apply specified decimal number formatting rules to _number_ and +return a formatted string. + +The second return value is (almost) the same formatted string divided +into four strings. It's a list of four strings: sign, integer part, +decimal separator and fractional part. Formatting arguments +_integer-minimum-width_ and _fractional-minimum-width_ do not apply to +the second return value. Everything else does. + +_Number_ must be of type `real`. This function uses `rational` types +internally. If the given _number_ is a `float` it is first turned into +`rational` by calling `cl:rational`. + +Formatting rules are specified with keyword arguments, as described +below. The default value is in parentheses. + + * `round-magnitude (0)` + + This is the order of magnitude used for rounding. The value must be + an integer and it is interpreted as a power of 10. + + * `show-trailing-zeros (nil)` + + If the value is non-nil print all trailing zeros in fractional part. + Examples: + + (format-decimal-number 1/5 :round-magnitude -3 + :show-trailing-zeros nil) + => \"0.2\" + + (format-decimal-number 1/5 :round-magnitude -3 + :show-trailing-zeros t) + => \"0.200\" + + * `rounder (#'round-half-away-from-zero)` + + The value must be a function (or a symbol naming a function). It is + used to round the number to the specified round magnitude. The + function must work like `cl:truncate`, `cl:floor`, `cl:ceiling` and + `cl:round`, that is, take two arguments, a number and a divisor, and + return the quotient as the first value. + + This package introduces another rounding function, + `round-half-away-from-zero`, which is used by default. See its + documentation for more information. + + * `decimal-separator (#\\.)` + + If the value is non-nil the `princ` output of the value will be + added between integer and fractional parts. Probably the most useful + types are `character` and `string`. + + * `integer-group-separator (nil)` + * `fractional-group-separator (nil)` + + If the value is non-nil the digits in integer or fractional parts + are put in groups. The `princ` output of the value will be added + between digit groups. + + * `integer-group-digits (3)` + * `fractional-group-digits (3)` + + The value is a positive integer defining the number of digits in + groups. + + * `integer-minimum-width (0)` + * `fractional-minimum-width (0)` + + Format integer or fractional part using minimum of this amount of + characters, possibly using some padding characters (see below). + _positive-sign_, _negative-sign_ or _zero-sign_ (see below) is + included when calculating the width of the integer part. Similarly + _decimal-separator_ is included when calculating the width of the + fractional part. + + * `integer-pad-char (#\\Space)` + * `fractional-pad-char (#\\Space)` + + The value is the padding character which is used to fill + _integer-minimum-width_ or _fractional-minimum-width_. + + * `positive-sign (nil)` + * `negative-sign (#\\-)` + * `zero-sign (nil)` + + If values are non-nil these are used as the leading sign for + positive, negative and zero numbers. The `princ` output of the value + is used." + + (destructuring-bind (sign integer fractional) + (decimal-round-split number + :round-magnitude round-magnitude + :rounder rounder + :positive-sign positive-sign + :negative-sign negative-sign + :zero-sign zero-sign) + + (setf decimal-separator (if decimal-separator + (princ-to-string decimal-separator) + "") + integer (divide-into-groups + integer + :separator (or integer-group-separator "") + :group-digits integer-group-digits + :from-end t) + fractional (divide-into-groups + (if (and show-trailing-zeros + (plusp (- (- (length fractional)) + round-magnitude))) + (replace (make-string (abs round-magnitude) + :initial-element #\0) + fractional) + fractional) + :separator (or fractional-group-separator "") + :group-digits fractional-group-digits + :from-end nil)) + + (values + (concatenate + 'string + (string-align (concatenate 'string sign integer) + integer-minimum-width + :side :right :char integer-pad-char) + (string-align (if (plusp (length fractional)) + (concatenate 'string decimal-separator fractional) + "") + fractional-minimum-width + :side :left :char fractional-pad-char)) + (list sign integer decimal-separator fractional)))) + + +(defmacro define-decimal-formatter (name &body keyword-arguments) + + "Define a decimal number formatter function to use with the `~/` +directive of `cl:format`. The valid format is this: + + (define-decimal-formatter name + (:keyword form) + ...) + +_Name_ is the symbol that names the function. _Keyword_ must be a valid +keyword argument for the `format-decimal-number` function (see its +documentation for more information). _Form_ is evaluated and the value +is used with the _keyword_ argument. Macro's side effect is that global +function _name_ is defined. It can be used with the `~/` directive of +`cl:format` function. + +Examples: + + (define-decimal-formatter my-formatter + (:round-magnitude -6) + (:decimal-separator \",\") + (:integer-group-separator \" \") + (:integer-minimum-width 4) + (:fractional-group-separator \" \") + (:fractional-minimum-width 10) + (:show-trailing-zeros t)) + => MY-FORMATTER + + (format nil \"~/my-formatter/\" 10/6) + => \" 1,666 667 \" + + (format nil \"~/my-formatter/\" 100/8) + => \" 12,500 000 \" + +The `~/` directive function call can optionally take up to three +arguments to override the defaults: + + ~round-magnitude,integer-minimum-width,fractional-minimum-width/FUNCTION/ + +For example: + + (format nil \"~-2,3,4/my-formatter/\" 10/6) + => \" 1,67 \"" + + (let ((key-arg (gensym))) + `(let ((,key-arg (list ,@(loop :for (keyword value) :in keyword-arguments + :do (assert (keywordp keyword) (keyword) + "Keyword required.") + :collect keyword :collect value)))) + + (defun ,name (stream number &optional colon-p at-sign-p + round-magnitude integer-minimum-width + fractional-minimum-width) + (declare (ignore colon-p at-sign-p)) + + (let ((args (copy-list ,key-arg))) + (when round-magnitude + (setf (getf args :round-magnitude) + round-magnitude)) + (when integer-minimum-width + (setf (getf args :integer-minimum-width) + integer-minimum-width)) + (when fractional-minimum-width + (setf (getf args :fractional-minimum-width) + fractional-minimum-width)) + (princ (apply #'format-decimal-number number args) stream)))))) + + +(defun number-string-to-integer (string) + (handler-case (parse-integer string) + (parse-error () nil))) + + +(defun number-string-to-fractional (string) + (when (every #'digit-char-p string) + (setf string (string-right-trim "0" string)) + (handler-case (/ (parse-integer string) + (expt 10 (length string))) + (parse-error () nil)))) + + +(define-condition decimal-parse-error (parse-error) + nil + (:report "Not a valid decimal number string.") + (:documentation + "Function `parse-decimal-number` signals this condition when it +couldn't parse a decimal number from string.")) + + +(defun parse-decimal-number (string &key + (decimal-separator #\.) + (positive-sign #\+) + (negative-sign #\-) + (start 0) (end nil)) + + "Examine _string_ (or its substring from _start_ to _end_) for a +decimal number. Assume that the decimal number is exact and return it as +a rational number. + +Rules for parsing: First all leading and trailing `#\\Space` characters +are stripped. The resulting string may start with a _positive-sign_ or a +_negative-sign_ character. The latter causes this function to assume a +negative number. The following characters in the string must include one +or more digit characters and it may include one _decimal-separator_ +character which separates integer and fractional parts. All other +characters are illegal. If these rules are not met a +`decimal-parse-error` condition is signaled. + +Examples: + + (parse-decimal-number \"0.2\") => 1/5 + (parse-decimal-number \".2\") => 1/5 + (parse-decimal-number \"+3.\") => 3 + (parse-decimal-number \" -7 \") => -7 + + (parse-decimal-number \"−12,345\" + :decimal-separator #\\, + :negative-sign #\\−) + => -2469/200" + + (setf string (string-trim " " (subseq string start end))) + (if (not (plusp (length string))) + (error 'decimal-parse-error) + (let ((sign 1)) + (cond ((char= (aref string 0) negative-sign) + (setf sign -1 + string (subseq string 1))) + ((char= (aref string 0) positive-sign) + (setf string (subseq string 1)))) + + (if (and (every (lambda (item) + (or (digit-char-p item) + (char= item decimal-separator))) + string) + (some #'digit-char-p string) + (<= 0 (count decimal-separator string) 1)) + + (let ((pos (position decimal-separator string))) + (* sign + (+ (or (number-string-to-integer (subseq string 0 pos)) + 0) + (if pos + (or (number-string-to-fractional + (subseq string (1+ pos))) + 0) + 0)))) + + (error 'decimal-parse-error))))) diff --git a/sql/expressions.lisp b/sql/expressions.lisp new file mode 100644 index 0000000..4f0baf1 --- /dev/null +++ b/sql/expressions.lisp @@ -0,0 +1,1247 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Classes defining SQL expressions and methods for formatting the +;;;; appropriate SQL commands. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defvar +empty-string+ "''") + +(defvar +null-string+ "NULL") + +(defvar *sql-stream* nil + "stream which accumulates SQL output") + +(defclass %database-identifier () + ((escaped :accessor escaped :initarg :escaped :initform nil) + (unescaped :accessor unescaped :initarg :unescaped :initform nil)) + (:documentation + "A database identifier represents a string/symbol ready to be spliced + into a sql string. It keeps references to both the escaped and + unescaped versions so that unescaped versions can be compared to the + results of list-tables/views/attributes etc. It also allows you to be + sure that an identifier is escaped only once. + + (escaped-database-identifiers *any-reasonable-object*) should be called to + produce a string that is safe to splice directly into sql strings. + + (unescaped-database-identifier *any-reasonable-object*) is generally what + you pass to it with the exception that symbols have been + clsql-sys:sql-escape which converts to a string and changes - to _ (so + that unescaped can be compared to the results of eg: list-tables) + ")) + +(defmethod escaped ((it null)) it) +(defmethod unescaped ((it null)) it) + +(defun database-identifier-equal (i1 i2 &optional (database clsql-sys:*default-database*)) + (setf i1 (database-identifier i1 database) + i2 (database-identifier i2 database)) + (flet ((cast (i) + (if (symbolp (unescaped i)) + (sql-escape (unescaped i)) + (unescaped i)))) + (or ;; check for an exact match + (equal (escaped-database-identifier i1) + (escaped-database-identifier i2)) + ;; check for an inexact match if we had symbols in the mix + (string-equal (cast i1) (cast i2))))) + +(defun delistify-dsd (list) + "Some MOPs, like openmcl 0.14.2, cons attribute values in a list." + (if (and (listp list) (null (cdr list))) + (car list) + list)) + +(defun special-char-p (s) + "Check if a string has any special characters" + (loop for char across s + thereis (find char '(#\space #\, #\. #\! #\@ #\# #\$ #\% #\' #\" + #\^ #\& #\* #\| #\( #\) #\- #\+ #\< #\> + #\{ #\})))) + +(defun special-cased-symbol-p (sym) + "Should the symbols case be preserved, or should we convert to default casing" + (let ((name (symbol-name sym))) + (case (readtable-case *readtable*) + (:upcase (not (string= (string-upcase name) name))) + (:downcase (not (string= (string-downcase name) name))) + (t t)))) + +(defun %make-database-identifier (inp &optional database) + "We want to quote an identifier if it came to us as a string or if it has special characters + in it." + (labels ((%escape-identifier (inp &optional orig) + "Quote an identifier unless it is already quoted" + (cond + ;; already quoted + ((and (eql #\" (elt inp 0)) + (eql #\" (elt inp (- (length inp) 1)))) + (make-instance '%database-identifier :unescaped (or orig inp) :escaped inp)) + (T (make-instance + '%database-identifier :unescaped (or orig inp) :escaped + (concatenate + 'string "\"" (replace-all inp "\"" "\\\"") "\"")))))) + (typecase inp + (string (%escape-identifier inp)) + (%database-identifier inp) + (symbol + (let ((s (sql-escape inp))) + (if (and (not (eql '* inp)) (special-char-p s)) + (%escape-identifier + (if (special-cased-symbol-p inp) + s + (convert-to-db-default-case s database)) inp) + (make-instance '%database-identifier :escaped s :unescaped inp)) + ))))) + +(defun combine-database-identifiers (ids &optional (database clsql-sys:*default-database*) + &aux res all-sym? pkg) + "Create a new database identifier by combining parts in a reasonable way + " + (setf ids (mapcar #'database-identifier ids) + all-sym? (every (lambda (i) (symbolp (unescaped i))) ids) + pkg (when all-sym? (symbol-package (unescaped (first ids))))) + (labels ((cast ( i ) + (typecase i + (null nil) + (%database-identifier (cast (unescaped i))) + (symbol + (if all-sym? + (sql-escape i) + (convert-to-db-default-case (sql-escape i) database))) + (string i))) + (comb (i1 i2) + (setf i1 (cast i1) + i2 (cast i2)) + (if (and i1 i2) + (concatenate 'string (cast i1) "_" (cast i2)) + (or i1 i2)))) + (setf res (reduce #'comb ids)) + (database-identifier + (if all-sym? (intern res pkg) res) + database))) + +(defun escaped-database-identifier (name &optional database find-class-p) + (escaped (database-identifier name database find-class-p))) + +(defun unescaped-database-identifier (name &optional database find-class-p) + (unescaped (database-identifier name database find-class-p))) + +(defun sql-output (sql-expr &optional (database *default-database*)) + "Top-level call for generating SQL strings. Returns an SQL + string appropriate for DATABASE which corresponds to the + supplied lisp expression SQL-EXPR." + (with-output-to-string (*sql-stream*) + (output-sql sql-expr database))) + +(defmethod output-sql (expr database) + (write-string (database-output-sql expr database) *sql-stream*) + (values)) + + +(defvar *output-hash* + (make-weak-hash-table :test #'equal) + "For caching generated SQL strings, set to NIL to disable." + ) + +(defmethod output-sql :around ((sql t) database) + (if (null *output-hash*) + (call-next-method) + (let* ((hash-key (output-sql-hash-key sql database)) + (hash-value (when hash-key (gethash hash-key *output-hash*)))) + (cond ((and hash-key hash-value) + (write-string hash-value *sql-stream*)) + (hash-key + (let ((*sql-stream* (make-string-output-stream))) + (call-next-method) + (setf hash-value (get-output-stream-string *sql-stream*)) + (setf (gethash hash-key *output-hash*) hash-value)) + (write-string hash-value *sql-stream*)) + (t + (call-next-method)))))) + +(defmethod output-sql-hash-key (expr database) + (declare (ignore expr database)) + nil) + + +(defclass %sql-expression () + ()) + +(defmethod output-sql ((expr %sql-expression) database) + (declare (ignore database)) + (write-string +null-string+ *sql-stream*)) + +(defmethod print-object ((self %sql-expression) stream) + (print-unreadable-object + (self stream :type t) + (write-string (sql-output self) stream)) + self) + +;; For straight up strings + +(defclass sql (%sql-expression) + ((text + :initarg :string + :initform "")) + (:documentation "A literal SQL expression.")) + +(defmethod make-load-form ((sql sql) &optional environment) + (declare (ignore environment)) + (with-slots (text) + sql + `(make-instance 'sql :string ',text))) + +(defmethod output-sql ((expr sql) database) + (declare (ignore database)) + (write-string (slot-value expr 'text) *sql-stream*) + t) + +(defmethod print-object ((ident sql) stream) + (format stream "#<~S \"~A\">" + (type-of ident) + (sql-output ident nil)) + ident) + +;; For SQL Identifiers of generic type + +(defclass sql-ident (%sql-expression) + ((name + :initarg :name + :initform +null-string+)) + (:documentation "An SQL identifer.")) + +(defmethod make-load-form ((sql sql-ident) &optional environment) + (declare (ignore environment)) + (with-slots (name) + sql + `(make-instance 'sql-ident :name ',name))) + +(defmethod output-sql ((expr %database-identifier) database) + (write-string (escaped expr) *sql-stream*)) + +(defmethod output-sql ((expr sql-ident) database) + (with-slots (name) expr + (write-string (escaped-database-identifier name database) *sql-stream*)) + t) + +;; For SQL Identifiers for attributes + +(defclass sql-ident-attribute (sql-ident) + ((qualifier + :initarg :qualifier + :initform +null-string+) + (type + :initarg :type + :initform +null-string+)) + (:documentation "An SQL Attribute identifier.")) + +(defmethod collect-table-refs (sql) + (declare (ignore sql)) + nil) + +(defmethod collect-table-refs ((sql list)) + (loop for i in sql + appending (listify (collect-table-refs i)))) + +(defmethod collect-table-refs ((sql sql-ident-attribute)) + (let ((qual (slot-value sql 'qualifier))) + (when qual + ;; going to be used as a table, search classes + (list (make-instance + 'sql-ident-table + :name (database-identifier qual nil t)))))) + +(defmethod make-load-form ((sql sql-ident-attribute) &optional environment) + (declare (ignore environment)) + (with-slots (qualifier type name) + sql + `(make-instance 'sql-ident-attribute :name ',name + :qualifier ',qualifier + :type ',type))) + +(defmethod output-sql-hash-key ((expr sql-ident-attribute) database) + (with-slots (qualifier name type) + expr + (list (and database (database-underlying-type database)) + 'sql-ident-attribute + (unescaped-database-identifier qualifier) + (unescaped-database-identifier name) type))) + +;; For SQL Identifiers for tables + +(defclass sql-ident-table (sql-ident) + ((alias + :initarg :table-alias :initform nil)) + (:documentation "An SQL table identifier.")) + +(defmethod make-load-form ((sql sql-ident-table) &optional environment) + (declare (ignore environment)) + (with-slots (alias name) + sql + `(make-instance 'sql-ident-table :name ',name :table-alias ',alias))) + +(defmethod collect-table-refs ((sql sql-ident-table)) + (list sql)) + +(defmethod output-sql ((expr sql-ident-table) database) + (with-slots (name alias) expr + (flet ((p (s) ;; the etypecase is in sql-escape too + (write-string + (escaped-database-identifier s database) + *sql-stream*))) + (p name) + (when alias + (princ #\space *sql-stream*) + (p alias)))) + t) + +(defmethod output-sql ((expr sql-ident-attribute) database) +;;; KMR: The TYPE field is used by CommonSQL for type conversion -- it +;;; should not be output in SQL statements + (let ((*print-pretty* nil)) + (with-slots (qualifier name type) expr + (format *sql-stream* "~@[~a.~]~a" + (when qualifier + ;; check for classes + (escaped-database-identifier qualifier database T)) + (escaped-database-identifier name database)) + t))) + +(defmethod output-sql-hash-key ((expr sql-ident-table) database) + (with-slots (name alias) + expr + (list (and database (database-underlying-type database)) + 'sql-ident-table + (unescaped-database-identifier name) + (unescaped-database-identifier alias)))) + +(defclass sql-relational-exp (%sql-expression) + ((operator + :initarg :operator + :initform nil) + (sub-expressions + :initarg :sub-expressions + :initform nil)) + (:documentation "An SQL relational expression.")) + +(defmethod make-load-form ((self sql-relational-exp) &optional environment) + (make-load-form-saving-slots self + :slot-names '(operator sub-expressions) + :environment environment)) + +(defmethod collect-table-refs ((sql sql-relational-exp)) + (let ((tabs nil)) + (dolist (exp (slot-value sql 'sub-expressions)) + (let ((refs (collect-table-refs exp))) + (if refs (setf tabs (append refs tabs))))) + (remove-duplicates tabs :test #'database-identifier-equal))) + + + + +;; Write SQL for relational operators (like 'AND' and 'OR'). +;; should do arity checking of subexpressions + +(defun %write-operator (operator database) + (typecase operator + (string (write-string operator *sql-stream*)) + (symbol (write-string (symbol-name operator) *sql-stream*)) + (T (output-sql operator database)))) + +(defmethod output-sql ((expr sql-relational-exp) database) + (with-slots (operator sub-expressions) expr + ;; we do this as two runs so as not to emit confusing superflous parentheses + ;; The first loop renders all the child outputs so that we can skip anding with + ;; empty output (which causes sql errors) + ;; the next loop simply emits each sub-expression with the appropriate number of + ;; parens and operators + (flet ((trim (sub) + (string-trim +whitespace-chars+ + (with-output-to-string (*sql-stream*) + (output-sql sub database))))) + (let ((str-subs (loop for sub in sub-expressions + for str-sub = (trim sub) + when (and str-sub (> (length str-sub) 0)) + collect str-sub))) + (case (length str-subs) + (0 nil) + (1 (write-string (first str-subs) *sql-stream*)) + (t + (write-char #\( *sql-stream*) + (write-string (first str-subs) *sql-stream*) + (loop for str-sub in (rest str-subs) + do + (write-char #\Space *sql-stream*) + ;; do this so that symbols can be output as database identifiers + ;; rather than allowing symbols to inject sql + (%write-operator operator database) + (write-char #\Space *sql-stream*) + (write-string str-sub *sql-stream*)) + (write-char #\) *sql-stream*)) + )))) + t) + +(defclass sql-upcase-like (sql-relational-exp) + () + (:documentation "An SQL 'like' that upcases its arguments.")) + +(defmethod output-sql ((expr sql-upcase-like) database) + (flet ((write-term (term) + (write-string "upper(" *sql-stream*) + (output-sql term database) + (write-char #\) *sql-stream*))) + (with-slots (sub-expressions) + expr + (let ((subs (if (consp (car sub-expressions)) + (car sub-expressions) + sub-expressions))) + (write-char #\( *sql-stream*) + (do ((sub subs (cdr sub))) + ((null (cdr sub)) (write-term (car sub))) + (write-term (car sub)) + (write-string " LIKE " *sql-stream*)) + (write-char #\) *sql-stream*)))) + t) + +(defclass sql-assignment-exp (sql-relational-exp) + () + (:documentation "An SQL Assignment expression.")) + + +(defmethod output-sql ((expr sql-assignment-exp) database) + (with-slots (operator sub-expressions) + expr + (do ((sub sub-expressions (cdr sub))) + ((null (cdr sub)) (output-sql (car sub) database)) + (output-sql (car sub) database) + (write-char #\Space *sql-stream*) + (%write-operator operator database) + (write-char #\Space *sql-stream*))) + t) + +(defclass sql-value-exp (%sql-expression) + ((modifier + :initarg :modifier + :initform nil) + (components + :initarg :components + :initform nil)) + (:documentation + "An SQL value expression.") + ) + +(defmethod collect-table-refs ((sql sql-value-exp)) + (let ((tabs nil)) + (if (listp (slot-value sql 'components)) + (progn + (dolist (exp (slot-value sql 'components)) + (let ((refs (collect-table-refs exp))) + (if refs (setf tabs (append refs tabs))))) + (remove-duplicates tabs :test #'database-identifier-equal)) + nil))) + +(defmethod output-sql ((expr sql-value-exp) database) + (with-slots (modifier components) + expr + (if modifier + (progn + (write-char #\( *sql-stream*) + (cond + ((sql-operator modifier) + (%write-operator modifier database)) + ((or (stringp modifier) (symbolp modifier)) + (write-string + (escaped-database-identifier modifier) + *sql-stream*)) + (t (output-sql modifier database))) + (write-char #\Space *sql-stream*) + (output-sql components database) + (write-char #\) *sql-stream*)) + (output-sql components database)))) + +(defclass sql-typecast-exp (sql-value-exp) + () + (:documentation "An SQL typecast expression.")) + +(defmethod output-sql ((expr sql-typecast-exp) database) + (with-slots (components) + expr + (output-sql components database))) + +(defmethod collect-table-refs ((sql sql-typecast-exp)) + (when (slot-value sql 'components) + (collect-table-refs (slot-value sql 'components)))) + +(defclass sql-function-exp (%sql-expression) + ((name + :initarg :name + :initform nil) + (args + :initarg :args + :initform nil)) + (:documentation + "An SQL function expression.")) + +(defmethod collect-table-refs ((sql sql-function-exp)) + (let ((tabs nil)) + (dolist (exp (slot-value sql 'args)) + (let ((refs (collect-table-refs exp))) + (if refs (setf tabs (append refs tabs))))) + (remove-duplicates tabs :test #'database-identifier-equal))) +(defvar *in-subselect* nil) + +(defmethod output-sql ((expr sql-function-exp) database) + (with-slots (name args) + expr + (typecase name + ((or string symbol) + (write-string (escaped-database-identifier name) *sql-stream*)) + (t (output-sql name database))) + (let ((*in-subselect* nil)) ;; aboid double parens + (when args (output-sql args database)))) + t) + + +(defclass sql-between-exp (sql-function-exp) + () + (:documentation "An SQL between expression.")) + +(defmethod output-sql ((expr sql-between-exp) database) + (with-slots (args) + expr + (output-sql (first args) database) + (write-string " BETWEEN " *sql-stream*) + (output-sql (second args) database) + (write-string " AND " *sql-stream*) + (output-sql (third args) database)) + t) + +(defclass sql-query-modifier-exp (%sql-expression) + ((modifier :initarg :modifier :initform nil) + (components :initarg :components :initform nil)) + (:documentation "An SQL query modifier expression.")) + +(defmethod output-sql ((expr sql-query-modifier-exp) database) + (with-slots (modifier components) + expr + (%write-operator modifier database) + (write-string " " *sql-stream*) + (%write-operator (car components) database) + (when components + (mapc #'(lambda (comp) + (write-string ", " *sql-stream*) + (output-sql comp database)) + (cdr components)))) + t) + +(defclass sql-set-exp (%sql-expression) + ((operator + :initarg :operator + :initform nil) + (sub-expressions + :initarg :sub-expressions + :initform nil)) + (:documentation "An SQL set expression.")) + +(defmethod collect-table-refs ((sql sql-set-exp)) + (let ((tabs nil)) + (dolist (exp (slot-value sql 'sub-expressions)) + (let ((refs (collect-table-refs exp))) + (if refs (setf tabs (append refs tabs))))) + (remove-duplicates tabs :test #'database-identifier-equal))) + +(defmethod output-sql ((expr sql-set-exp) database) + (with-slots (operator sub-expressions) + expr + (let ((subs (if (consp (car sub-expressions)) + (car sub-expressions) + sub-expressions))) + (when (= (length subs) 1) + (%write-operator operator database) + (write-char #\Space *sql-stream*)) + (do ((sub subs (cdr sub))) + ((null (cdr sub)) (output-sql (car sub) database)) + (output-sql (car sub) database) + (write-char #\Space *sql-stream*) + (%write-operator operator database) + (write-char #\Space *sql-stream*)))) + t) + +(defclass sql-query (%sql-expression) + ((selections + :initarg :selections + :initform nil) + (all + :initarg :all + :initform nil) + (flatp + :initarg :flatp + :initform nil) + (set-operation + :initarg :set-operation + :initform nil) + (distinct + :initarg :distinct + :initform nil) + (from + :initarg :from + :initform nil) + (where + :initarg :where + :initform nil) + (group-by + :initarg :group-by + :initform nil) + (having + :initarg :having + :initform nil) + (limit + :initarg :limit + :initform nil) + (offset + :initarg :offset + :initform nil) + (order-by + :initarg :order-by + :initform nil) + (inner-join + :initarg :inner-join + :initform nil) + (on + :initarg :on + :initform nil)) + (:documentation "An SQL SELECT query.")) + +(defclass sql-object-query (%sql-expression) + ((objects + :initarg :objects + :initform nil) + (flatp + :initarg :flatp + :initform nil) + (exp + :initarg :exp + :initform nil) + (refresh + :initarg :refresh + :initform nil))) + +(defmethod collect-table-refs ((sql sql-query)) + (remove-duplicates + (collect-table-refs (slot-value sql 'where)) + :test #'database-identifier-equal)) + +(defvar *select-arguments* + '(:all :database :distinct :flatp :from :group-by :having :order-by + :set-operation :where :offset :limit :inner-join :on + ;; below keywords are not a SQL argument, but these keywords may terminate select + :caching :refresh)) + +(defun query-arg-p (sym) + (member sym *select-arguments*)) + +(defun query-get-selections (select-args) + "Return two values: the list of select-args up to the first keyword, +uninclusive, and the args from that keyword to the end." + (let ((first-key-arg (position-if #'query-arg-p select-args))) + (if first-key-arg + (values (subseq select-args 0 first-key-arg) + (subseq select-args first-key-arg)) + select-args))) + +(defun make-query (&rest args) + (flet ((select-objects (target-args) + (and target-args + (every #'(lambda (arg) + (and (symbolp arg) + (find-class arg nil))) + target-args)))) + (multiple-value-bind (selections arglist) + (query-get-selections args) + (if (select-objects selections) + (destructuring-bind (&key flatp refresh &allow-other-keys) arglist + (make-instance 'sql-object-query :objects selections + :flatp flatp :refresh refresh + :exp arglist)) + (destructuring-bind (&key all flatp set-operation distinct from where + group-by having order-by + offset limit inner-join on &allow-other-keys) + arglist + (if (null selections) + (error "No target columns supplied to select statement.")) + (if (null from) + (error "No source tables supplied to select statement.")) + (make-instance 'sql-query :selections selections + :all all :flatp flatp :set-operation set-operation + :distinct distinct :from from :where where + :limit limit :offset offset + :group-by group-by :having having :order-by order-by + :inner-join inner-join :on on)))))) + +(defun output-sql-where-clause (where database) + "ensure that we do not output a \"where\" sql keyword when we will + not output a clause. Also sets *in-subselect* to use SQL + parentheticals as needed." + (when where + (let ((where-out (string-trim + '(#\newline #\space #\tab #\return) + (with-output-to-string (*sql-stream*) + (let ((*in-subselect* t)) + (output-sql where database)))))) + (when (> (length where-out) 0) + (write-string " WHERE " *sql-stream*) + (write-string where-out *sql-stream*))))) + +(defmethod output-sql ((query sql-query) database) + (with-slots (distinct selections from where group-by having order-by + limit offset inner-join on all set-operation) + query + (when *in-subselect* + (write-string "(" *sql-stream*)) + (write-string "SELECT " *sql-stream*) + (when all + (write-string " ALL " *sql-stream*)) + (when (and distinct (not all)) + (write-string " DISTINCT " *sql-stream*) + (unless (eql t distinct) + (write-string " ON " *sql-stream*) + (output-sql distinct database) + (write-char #\Space *sql-stream*))) + (when (and limit (eql :mssql (database-underlying-type database))) + (write-string " TOP " *sql-stream*) + (output-sql limit database) + (write-string " " *sql-stream*)) + (let ((*in-subselect* t)) + (output-sql (apply #'vector selections) database)) + (when from + (write-string " FROM " *sql-stream*) + (typecase from + (list (output-sql + (apply #'vector + (remove-duplicates from :test #'database-identifier-equal)) + database)) + (string (write-string + (escaped-database-identifier from database) + *sql-stream*)) + (t (let ((*in-subselect* t)) + (output-sql from database))))) + (when inner-join + (write-string " INNER JOIN " *sql-stream*) + (output-sql inner-join database)) + (when on + (write-string " ON " *sql-stream*) + (output-sql on database)) + (output-sql-where-clause where database) + (when group-by + (write-string " GROUP BY " *sql-stream*) + (if (listp group-by) + (do ((order group-by (cdr order))) + ((null order)) + (let ((item (car order))) + (typecase item + (cons + (output-sql (car item) database) + (format *sql-stream* " ~A" (cadr item))) + (t + (output-sql item database))) + (when (cdr order) + (write-char #\, *sql-stream*)))) + (output-sql group-by database))) + (when having + (write-string " HAVING " *sql-stream*) + (output-sql having database)) + (when order-by + (write-string " ORDER BY " *sql-stream*) + (if (listp order-by) + (do ((order order-by (cdr order))) + ((null order)) + (let ((item (car order))) + (typecase item + (cons + (output-sql (car item) database) + (format *sql-stream* " ~A" (cadr item))) + (t + (output-sql item database))) + (when (cdr order) + (write-char #\, *sql-stream*)))) + (output-sql order-by database))) + (when (and limit (not (eql :mssql (database-underlying-type database)))) + (write-string " LIMIT " *sql-stream*) + (output-sql limit database)) + (when offset + (write-string " OFFSET " *sql-stream*) + (output-sql offset database)) + (when *in-subselect* + (write-string ")" *sql-stream*)) + (when set-operation + (write-char #\Space *sql-stream*) + (output-sql set-operation database))) + t) + +(defmethod output-sql ((query sql-object-query) database) + (declare (ignore database)) + (with-slots (objects) + query + (when objects + (format *sql-stream* "(~{~A~^ ~})" objects)))) + + +;; INSERT + +(defclass sql-insert (%sql-expression) + ((into + :initarg :into + :initform nil) + (attributes + :initarg :attributes + :initform nil) + (values + :initarg :values + :initform nil) + (query + :initarg :query + :initform nil)) + (:documentation + "An SQL INSERT statement.")) + +(defmethod output-sql ((ins sql-insert) database) + (with-slots (into attributes values query) + ins + (write-string "INSERT INTO " *sql-stream*) + (output-sql + (typecase into + (string (sql-expression :table into)) + (t into)) + database) + (when attributes + (write-char #\Space *sql-stream*) + (output-sql attributes database)) + (when values + (write-string " VALUES " *sql-stream*) + (let ((clsql-sys::*in-subselect* t)) + (output-sql values database))) + (when query + (write-char #\Space *sql-stream*) + (output-sql query database))) + t) + +;; DELETE + +(defclass sql-delete (%sql-expression) + ((from + :initarg :from + :initform nil) + (where + :initarg :where + :initform nil)) + (:documentation + "An SQL DELETE statement.")) + +(defmethod output-sql ((stmt sql-delete) database) + (with-slots (from where) + stmt + (write-string "DELETE FROM " *sql-stream*) + (typecase from + ((or symbol string) (write-string (sql-escape from) *sql-stream*)) + (t (output-sql from database))) + (output-sql-where-clause where database)) + t) + +;; UPDATE + +(defclass sql-update (%sql-expression) + ((table + :initarg :table + :initform nil) + (attributes + :initarg :attributes + :initform nil) + (values + :initarg :values + :initform nil) + (where + :initarg :where + :initform nil)) + (:documentation "An SQL UPDATE statement.")) + +(defmethod output-sql ((expr sql-update) database) + (with-slots (table where attributes values) + expr + (flet ((update-assignments () + (mapcar #'(lambda (a b) + (make-instance 'sql-assignment-exp + :operator '= + :sub-expressions (list a b))) + attributes values))) + (write-string "UPDATE " *sql-stream*) + (output-sql table database) + (write-string " SET " *sql-stream*) + (let ((clsql-sys::*in-subselect* t)) + (output-sql (apply #'vector (update-assignments)) database)) + (output-sql-where-clause where database))) + t) + +;; CREATE TABLE + +(defclass sql-create-table (%sql-expression) + ((name + :initarg :name + :initform nil) + (columns + :initarg :columns + :initform nil) + (modifiers + :initarg :modifiers + :initform nil) + (transactions + :initarg :transactions + :initform nil)) + (:documentation + "An SQL CREATE TABLE statement.")) + +;; Here's a real warhorse of a function! + +(declaim (inline listify)) +(defun listify (x) + (if (listp x) + x + (list x))) + +(defmethod output-sql ((stmt sql-create-table) database) + (flet ((output-column (column-spec) + (destructuring-bind (name type &optional db-type &rest constraints) + column-spec + (let ((type (listify type))) + (output-sql name database) + (write-char #\Space *sql-stream*) + (write-string + (if (stringp db-type) db-type ; override definition + (database-get-type-specifier (car type) (cdr type) database + (database-underlying-type database))) + *sql-stream*) + (let ((constraints (database-constraint-statement + (if (and db-type (symbolp db-type)) + (cons db-type constraints) + constraints) + database))) + (when constraints + (write-string " " *sql-stream*) + (write-string constraints *sql-stream*))))))) + (with-slots (name columns modifiers transactions) + stmt + (write-string "CREATE TABLE " *sql-stream*) + (write-string (escaped-database-identifier name database) *sql-stream*) + (write-string " (" *sql-stream*) + (do ((column columns (cdr column))) + ((null (cdr column)) + (output-column (car column))) + (output-column (car column)) + (write-string ", " *sql-stream*)) + (when modifiers + (do ((modifier (listify modifiers) (cdr modifier))) + ((null modifier)) + (write-string ", " *sql-stream*) + (write-string (car modifier) *sql-stream*))) + (write-char #\) *sql-stream*) + (when (and (eq :mysql (database-underlying-type database)) + transactions + (db-type-transaction-capable? :mysql database)) + (write-string " ENGINE=innodb" *sql-stream*)))) + t) + + +;; CREATE VIEW + +(defclass sql-create-view (%sql-expression) + ((name :initarg :name :initform nil) + (column-list :initarg :column-list :initform nil) + (query :initarg :query :initform nil) + (with-check-option :initarg :with-check-option :initform nil)) + (:documentation "An SQL CREATE VIEW statement.")) + +(defmethod output-sql ((stmt sql-create-view) database) + (with-slots (name column-list query with-check-option) stmt + (write-string "CREATE VIEW " *sql-stream*) + (output-sql name database) + (when column-list (write-string " " *sql-stream*) + (output-sql (listify column-list) database)) + (write-string " AS " *sql-stream*) + (output-sql query database) + (when with-check-option (write-string " WITH CHECK OPTION" *sql-stream*)))) + + +;; +;; DATABASE-OUTPUT-SQL +;; + +(defmethod database-output-sql ((str string) database) + (declare (optimize (speed 3) (safety 1) + #+cmu (extensions:inhibit-warnings 3))) + (let ((len (length str))) + (declare (type fixnum len)) + (cond ((zerop len) + +empty-string+) + ((and (null (position #\' str)) + (null (position #\\ str))) + (concatenate 'string "'" str "'")) + (t + (let ((buf (make-string (+ (* len 2) 2) :initial-element #\'))) + (declare (simple-string buf)) + (do* ((i 0 (incf i)) + (j 1 (incf j))) + ((= i len) (subseq buf 0 (1+ j))) + (declare (type fixnum i j)) + (let ((char (aref str i))) + (declare (character char)) + (cond ((char= char #\') + (setf (aref buf j) #\') + (incf j) + (setf (aref buf j) #\')) + ((and (char= char #\\) + ;; MTP: only escape backslash with pgsql/mysql + (member (database-underlying-type database) + '(:postgresql :mysql) + :test #'eq)) + (setf (aref buf j) #\\) + (incf j) + (setf (aref buf j) #\\)) + (t + (setf (aref buf j) char)))))))))) + +(let ((keyword-package (symbol-package :foo))) + (defmethod database-output-sql ((sym symbol) database) + (if (null sym) + +null-string+ + (if (equal (symbol-package sym) keyword-package) + (database-output-sql (symbol-name sym) database) + (escaped-database-identifier sym))))) + +(defmethod database-output-sql ((tee (eql t)) database) + (if database + (let ((val (database-output-sql-as-type 'boolean t database (database-type database)))) + (when val + (typecase val + (string (format nil "'~A'" val)) + (integer (format nil "~A" val))))) + "'Y'")) + +#+nil(defmethod database-output-sql ((tee (eql t)) database) + (declare (ignore database)) + "'Y'") + +(defmethod database-output-sql ((num number) database) + (declare (ignore database)) + (number-to-sql-string num)) + +(defmethod database-output-sql ((arg list) database) + (if (null arg) + +null-string+ + (format nil "(~{~A~^,~})" (mapcar #'(lambda (val) + (sql-output val database)) + arg)))) + +(defmethod database-output-sql ((arg vector) database) + (format nil "~{~A~^,~}" (map 'list #'(lambda (val) + (sql-output val database)) + arg))) + +(defmethod output-sql-hash-key ((arg vector) database) + (list 'vector (map 'list (lambda (arg) + (or (output-sql-hash-key arg database) + (return-from output-sql-hash-key nil))) + arg))) + +(defmethod database-output-sql ((self wall-time) database) + (declare (ignore database)) + (db-timestring self)) + +(defmethod database-output-sql ((self date) database) + (declare (ignore database)) + (db-datestring self)) + +(defmethod database-output-sql ((self duration) database) + (declare (ignore database)) + (format nil "'~a'" (duration-timestring self))) + +#+ignore +(defmethod database-output-sql ((self money) database) + (database-output-sql (slot-value self 'odcl::units) database)) + +(defmethod database-output-sql (thing database) + (if (or (null thing) + (eq 'null thing)) + +null-string+ + (error 'sql-user-error + :message + (format nil + "No type conversion to SQL for ~A is defined for DB ~A." + (type-of thing) (type-of database))))) + + +;; +;; Column constraint types and conversion to SQL +;; +(defmethod database-constraint-statement (constraint-list database) + (make-constraints-description constraint-list database)) + +;; KEEP THIS SYNCED WITH database-translate-constraint +(defparameter +auto-increment-names+ + '(:auto-increment :auto_increment :autoincrement :identity)) + +(defmethod database-translate-constraint (constraint database) + (case constraint + (:not-null "NOT NULL") + (:primary-key "PRIMARY KEY") + ((:auto-increment :auto_increment :autoincrement :identity) + (ecase (database-underlying-type database) + (:mssql "IDENTITY (1,1)") + ((:sqlite :sqlite3) "PRIMARY KEY AUTOINCREMENT") + (:mysql "AUTO_INCREMENT") + ;; this is modeled as a datatype instead of a constraint + (:postgresql ""))) + ;; everything else just get the name + (T (string-upcase (symbol-name constraint))))) + +(defun make-constraints-description (constraint-list database + &aux (rest constraint-list) constraint) + (when constraint-list + (flet ((next () + (setf constraint (first rest) + rest (rest rest)) + constraint)) + (with-output-to-string (s) + (loop while (next) + do (unless (keywordp constraint) + (setf constraint (intern (symbol-name constraint) :keyword))) + (write-string (database-translate-constraint constraint database) s) + (when (eql :default constraint) (princ (next) s)) + (write-char #\space s) + ))))) + +(defmethod database-identifier ( name &optional database find-class-p + &aux cls) + "A function that takes whatever you give it, recursively coerces it, + and returns a database-identifier. + + (escaped-database-identifiers *any-reasonable-object*) should be called to + produce a string that is safe to splice directly into sql strings. + + This function should NOT throw errors when database is nil + + find-class-p should be T if we want to search for classes + and check their use their view table. Should be used + on symbols we are sure indicate tables + + + ;; metaclasses has further typecases of this, so that it will + ;; load less painfully (try-recompiles) in SBCL + + " + (flet ((flatten-id (id) + "if we have multiple pieces that we need to represent as + db-id lets do that by rendering out the id, then creating + a new db-id with that string as escaped" + (let ((s (sql-output id database))) + (make-instance '%database-identifier :escaped s :unescaped s)))) + (setf name (dequote name)) + (etypecase name + (null nil) + (string (%make-database-identifier name database)) + (symbol + ;; if this is being used as a table, we should check + ;; for a class with this name and use the identifier specified + ;; on it + (if (and find-class-p (setf cls (find-standard-db-class name))) + (database-identifier cls) + (%make-database-identifier name database))) + (%database-identifier name) + ;; we know how to deref this without further escaping + (sql-ident-table + (with-slots ((inner-name name) alias) name + (if alias + (flatten-id name) + (database-identifier inner-name)))) + ;; if this is a single name we can derefence it + (sql-ident-attribute + (with-slots (qualifier (inner-name name)) name + (if qualifier + (flatten-id name) + (database-identifier inner-name)))) + (sql-ident + (with-slots ((inner-name name)) name + (database-identifier inner-name))) + ;; dont know how to handle this really :/ + (%sql-expression (flatten-id name)) + ))) + +(defun %clsql-subclauses (clauses) + "a helper for dealing with lists of sql clauses" + (loop for c in clauses + when c + collect (typecase c + (string (clsql-sys:sql-expression :string c)) + (T c)))) + +(defun clsql-ands (clauses) + "Correctly creates a sql 'and' expression for the clauses + ignores any nil clauses + returns a single child expression if there is only one + returns an 'and' expression if there are many + returns nil if there are no children" + (let ((ex (%clsql-subclauses clauses))) + (when ex + (case (length ex) + (1 (first ex)) + (t (apply #'clsql-sys:sql-and ex)))))) + +(defun clsql-and (&rest clauses) + "Correctly creates a sql 'and' expression for the clauses + ignores any nil clauses + returns a single child expression if there is only one + returns an 'and' expression if there are many + returns nil if there are no children" + (clsql-ands clauses)) + +(defun clsql-ors (clauses) + "Correctly creates a sql 'or' expression for the clauses + ignores any nil clauses + returns a single child expression if there is only one + returns an 'or' expression if there are many + returns nil if there are no children" + (let ((ex (%clsql-subclauses clauses))) + (when ex + (case (length ex) + (1 (first ex)) + (t (apply #'clsql-sys:sql-or ex)))))) + +(defun clsql-or (&rest clauses) + "Correctly creates a sql 'or' expression for the clauses + ignores any nil clauses + returns a single child expression if there is only one + returns an 'or' expression if there are many + returns nil if there are no children" + (clsql-ors clauses)) + + +(defclass sql-escape-string-exp (%sql-expression) + ((string + :initarg :string + :initform nil)) + (:documentation + "An escaped string string expression (postgresql E'stuff') .")) + +(defmethod output-sql ((exp sql-escape-string-exp) database) + (with-slots (string) exp + (when string + (write-char #\E *sql-stream*) + (output-sql string database)))) diff --git a/sql/fddl.lisp b/sql/fddl.lisp new file mode 100644 index 0000000..c4fc195 --- /dev/null +++ b/sql/fddl.lisp @@ -0,0 +1,437 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; The CLSQL Functional Data Definition Language (FDDL) +;;;; including functions for schema manipulation. Currently supported +;;;; SQL objects include tables, views, indexes, attributes and +;;;; sequences. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + + +;; Truncate database + +(defun truncate-database (&key (database *default-database*)) + "Drops all tables, views, indexes and sequences in DATABASE which +defaults to *DEFAULT-DATABASE*." + (unless (typep database 'database) + (signal-no-database-error database)) + (unless (is-database-open database) + (database-reconnect database)) + (when (eq :oracle (database-type database)) + (ignore-errors (execute-command "PURGE RECYCLEBIN" :database database))) + (when (db-type-has-views? (database-underlying-type database)) + (dolist (view (list-views :database database)) + (drop-view view :database database))) + (dolist (table (list-tables :database database)) + (drop-table table :database database)) + (dolist (index (list-indexes :database database)) + (drop-index index :database database)) + (dolist (seq (list-sequences :database database)) + (drop-sequence seq :database database)) + (when (eq :oracle (database-type database)) + (ignore-errors (execute-command "PURGE RECYCLEBIN" :database database))) + (values)) + + +;; Tables + +(defun create-table (name description &key (database *default-database*) + (constraints nil) (transactions t)) + "Creates a table called NAME, which may be a string, symbol or +SQL table identifier, in DATABASE which defaults to +*DEFAULT-DATABASE*. DESCRIPTION is a list whose elements are +lists containing the attribute names, types, and other +constraints such as not-null or primary-key for each column in +the table. CONSTRAINTS is a string representing an SQL table +constraint expression or a list of such strings. With MySQL +databases, if TRANSACTIONS is t an InnoDB table is created which +supports transactions." + (execute-command + (make-instance 'sql-create-table + :name name + :columns description + :modifiers constraints + :transactions transactions) + :database database)) + +(defun drop-table (name &key (if-does-not-exist :error) + (database *default-database*) + (owner nil)) + "Drops the table called NAME from DATABASE which defaults to +*DEFAULT-DATABASE*. If the table does not exist and +IF-DOES-NOT-EXIST is :ignore then DROP-TABLE returns nil whereas +an error is signalled if IF-DOES-NOT-EXIST is :error." + (ecase if-does-not-exist + (:ignore + (unless (table-exists-p name :database database :owner owner) + (return-from drop-table nil))) + (:error + t)) + + (let ((expr (concatenate 'string "DROP TABLE " (escaped-database-identifier name database)))) + ;; Fixme: move to clsql-oracle + (when (and (find-package 'clsql-oracle) + (eq :oracle (database-type database)) + (eql 10 (slot-value database + (intern (symbol-name '#:major-server-version) + (symbol-name '#:clsql-oracle))))) + (setq expr (concatenate 'string expr " PURGE"))) + + (execute-command expr :database database))) + +(defun list-tables (&key (owner nil) (database *default-database*)) + "Returns a list of strings representing table names in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only tables owned by users are listed. If OWNER +is a string denoting a user name, only tables owned by OWNER are +listed. If OWNER is :all then all tables are listed." + (database-list-tables database :owner owner)) + +(defmethod %table-exists-p (name (database T) &key owner ) + (unless database (setf database *default-database*)) + (let ((name (database-identifier name database)) + (tables (list-tables :owner owner :database database))) + (when (member name tables :test #'database-identifier-equal) + t))) + +(defun table-exists-p (name &key (owner nil) (database *default-database*)) + "Tests for the existence of an SQL table called NAME in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only tables owned by users are examined. If +OWNER is a string denoting a user name, only tables owned by +OWNER are examined. If OWNER is :all then all tables are +examined." + (%table-exists-p name database :owner owner)) + + +;; Views + +(defun create-view (name &key as column-list (with-check-option nil) + (database *default-database*)) + "Creates a view called NAME in DATABASE which defaults to +*DEFAULT-DATABASE*. The view is created using the query AS and +the columns of the view may be specified using the COLUMN-LIST +parameter. The WITH-CHECK-OPTION is nil by default but if it has +a non-nil value, then all insert/update commands on the view are +checked to ensure that the new data satisfy the query AS." + (let* ((view-name (database-identifier name)) + (stmt (make-instance 'sql-create-view + :name view-name + :column-list column-list + :query as + :with-check-option with-check-option))) + (execute-command stmt :database database))) + +(defun drop-view (name &key (if-does-not-exist :error) + (database *default-database*)) + "Drops the view called NAME from DATABASE which defaults to +*DEFAULT-DATABASE*. If the view does not exist and +IF-DOES-NOT-EXIST is :ignore then DROP-VIEW returns nil whereas +an error is signalled if IF-DOES-NOT-EXIST is :error." + (ecase if-does-not-exist + (:ignore + (unless (view-exists-p name :database database) + (return-from drop-view))) + (:error + t)) + (let ((expr (concatenate 'string "DROP VIEW " (escaped-database-identifier name database)))) + (execute-command expr :database database))) + +(defun list-views (&key (owner nil) (database *default-database*)) + "Returns a list of strings representing view names in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only views owned by users are listed. If OWNER +is a string denoting a user name, only views owned by OWNER are +listed. If OWNER is :all then all views are listed." + (database-list-views database :owner owner)) + +(defun view-exists-p (name &key (owner nil) (database *default-database*)) + "Tests for the existence of an SQL view called NAME in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only views owned by users are examined. If OWNER +is a string denoting a user name, only views owned by OWNER are +examined. If OWNER is :all then all views are examined." + (when (member (database-identifier name database) + (list-views :owner owner :database database) + :test #'database-identifier-equal) + t)) + + +;; Indexes + +(defun create-index (name &key on (unique nil) attributes + (database *default-database*)) + "Creates an index called NAME on the table specified by ON in +DATABASE which default to *DEFAULT-DATABASE*. The table +attributes to use in constructing the index NAME are specified by +ATTRIBUTES. The UNIQUE argument is nil by default but if it has a +non-nil value then the indexed attributes must have unique +values." + (let* ((index-name (escaped-database-identifier name database)) + (table-name (escaped-database-identifier on database)) + (attributes (mapcar #'(lambda (a) (escaped-database-identifier a database)) + (listify attributes))) + (stmt (format nil "CREATE ~A INDEX ~A ON ~A (~{~A~^, ~})" + (if unique "UNIQUE" "") + index-name table-name attributes))) + (execute-command stmt :database database))) + +(defun drop-index (name &key (if-does-not-exist :error) + (on nil) + (database *default-database*)) + "Drops the index called NAME in DATABASE which defaults to +*DEFAULT-DATABASE*. If the index does not exist and +IF-DOES-NOT-EXIST is :ignore then DROP-INDEX returns nil whereas +an error is signalled if IF-DOES-NOT-EXIST is :error. The +argument ON allows the optional specification of a table to drop +the index from." + (ecase if-does-not-exist + (:ignore + (unless (index-exists-p name :database database) + (return-from drop-index))) + (:error t)) + (let* ((db-type (database-underlying-type database)) + (on (when on (escaped-database-identifier on database))) + (index-name (escaped-database-identifier name database)) + (index-identifier + (cond ((db-type-use-fully-qualified-column-on-drop-index? db-type) + (format nil "~A.~A" on index-name)) + ((db-type-use-column-on-drop-index? db-type) + (format nil "~A ON ~A" index-name on)) + (t index-name)))) + (execute-command (format nil "DROP INDEX ~A" index-identifier) + :database database))) + +(defun list-indexes (&key (owner nil) (database *default-database*) (on nil)) + "Returns a list of strings representing index names in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only indexes owned by users are listed. If OWNER +is a string denoting a user name, only indexes owned by OWNER are +listed. If OWNER is :all then all indexes are listed. The keyword +argument ON limits the results to indexes on the specified +tables. Meaningful values for ON are nil (the default) which +means that all tables are considered, a string, symbol or SQL +expression representing a table name in DATABASE or a list of +such table identifiers." + (if (null on) + (database-list-indexes database :owner owner) + (let ((tables (typecase on + (cons on) + (t (list on))))) + (reduce + #'append + (mapcar #'(lambda (table) + (database-list-table-indexes table database :owner owner)) + tables))))) + +(defun index-exists-p (name &key (owner nil) (database *default-database*)) + "Tests for the existence of an SQL index called NAME in DATABASE +which defaults to *DEFAULT-DATABASE*. OWNER is nil by default +which means that only indexes owned by users are examined. If +OWNER is a string denoting a user name, only indexes owned by +OWNER are examined. If OWNER is :all then all indexes are +examined." + (when (member (database-identifier name database) + (list-indexes :owner owner :database database) + :test #'database-identifier-equal) + t)) + +;; Attributes + +(defvar *cache-table-queries-default* nil + "Specifies the default behaivour for caching of attribute + types. Meaningful values are t, nil and :flush as described for + the action argument to CACHE-TABLE-QUERIES.") + +(defun cache-table-queries (table &key (action nil) (database *default-database*)) + "Controls the caching of attribute type information on the +table specified by TABLE in DATABASE which defaults to +*DEFAULT-DATABASE*. ACTION specifies the caching behaviour to +adopt. If its value is t then attribute type information is +cached whereas if its value is nil then attribute type +information is not cached. If ACTION is :flush then all existing +type information in the cache for TABLE is removed, but caching +is still enabled. TABLE may be a string representing a table for +which the caching action is to be taken while the caching action +is applied to all tables if TABLE is t. Alternativly, when TABLE +is :default, the default caching action specified by +*CACHE-TABLE-QUERIES-DEFAULT* is applied to all table for which a +caching action has not been explicitly set." + (with-slots (attribute-cache) database + (cond + ((stringp table) + (multiple-value-bind (val found) (gethash table attribute-cache) + (cond + ((and found (eq action :flush)) + (setf (gethash table attribute-cache) (list t nil))) + ((and found (eq action t)) + (setf (gethash table attribute-cache) (list t (second val)))) + ((and found (null action)) + (setf (gethash table attribute-cache) (list nil nil))) + ((not found) + (setf (gethash table attribute-cache) (list action nil)))))) + ((eq table t) + (maphash (lambda (k v) + (cond + ((eq action :flush) + (setf (gethash k attribute-cache) (list t nil))) + ((null action) + (setf (gethash k attribute-cache) (list nil nil))) + ((eq t action) + (setf (gethash k attribute-cache) (list t (second v)))))) + attribute-cache)) + ((eq table :default) + (maphash (lambda (k v) + (when (eq (first v) :unspecified) + (cond + ((eq action :flush) + (setf (gethash k attribute-cache) (list t nil))) + ((null action) + (setf (gethash k attribute-cache) (list nil nil))) + ((eq t action) + (setf (gethash k attribute-cache) (list t (second v))))))) + attribute-cache)))) + (values)) + + +(defun list-attributes (name &key (owner nil) (database *default-database*)) + "Returns a list of strings representing the attributes of table +NAME in DATABASE which defaults to *DEFAULT-DATABASE*. OWNER is +nil by default which means that only attributes owned by users +are listed. If OWNER is a string denoting a user name, only +attributes owned by OWNER are listed. If OWNER is :all then all +attributes are listed." + (database-list-attributes + (database-identifier name database) + database + :owner owner)) + +(defun attribute-type (attribute table &key (owner nil) + (database *default-database*)) + "Returns a keyword representing the vendor-specific field type +of the supplied attribute ATTRIBUTE in the table specified by +TABLE in DATABASE which defaults to *DEFAULT-DATABASE*. OWNER is +nil by default which means that the attribute specified by +ATTRIBUTE, if it exists, must be user owned else nil is +returned. If OWNER is a string denoting a user name, the +attribute, if it exists, must be owned by OWNER else nil is +returned, whereas if OWNER is :all then the attribute, if it +exists, will be returned regardless of its owner." + (database-attribute-type (database-identifier attribute database) + (database-identifier table database) + database + :owner owner)) + +(defun list-attribute-types (table &key (owner nil) + (database *default-database*)) + "Returns a list containing information about the SQL types of +each of the attributes in the table specified by TABLE in +DATABASE which has a default value of *DEFAULT-DATABASE*. OWNER +is nil by default which means that only attributes owned by users +are listed. If OWNER is a string denoting a user name, only +attributes owned by OWNER are listed. If OWNER is :all then all +attributes are listed. The elements of the returned list are +lists where the first element is the name of the attribute, the +second element is its SQL type, the third is the type precision, +the fourth is the scale of the attribute and the fifth is 1 if +the attribute accepts null values and otherwise 0." + (with-slots (attribute-cache) database + (let ((table-ident (database-identifier table database))) + (multiple-value-bind (val found) + (gethash table attribute-cache) + (if (and found (second val)) + (second val) + (let ((types (mapcar #'(lambda (attribute) + (cons attribute + (multiple-value-list + (database-attribute-type + (database-identifier attribute + database) + table-ident + database + :owner owner)))) + (list-attributes table :database database + :owner owner)))) + (cond + ((and (not found) (eq t *cache-table-queries-default*)) + (setf (gethash table attribute-cache) + (list :unspecified types))) + ((and found (eq t (first val)) + (setf (gethash table attribute-cache) + (list t types))))) + types)))))) + + +;; Sequences + +(defun create-sequence (name &key (database *default-database*)) + "Creates a sequence called NAME in DATABASE which defaults to +*DEFAULT-DATABASE*." + (let ((sequence-name (database-identifier name database))) + (database-create-sequence sequence-name database)) + (values)) + +(defun drop-sequence (name &key (if-does-not-exist :error) + (database *default-database*)) + "Drops the sequence called NAME from DATABASE which defaults to +*DEFAULT-DATABASE*. If the sequence does not exist and +IF-DOES-NOT-EXIST is :ignore then DROP-SEQUENCE returns nil +whereas an error is signalled if IF-DOES-NOT-EXIST is :error." + (ecase if-does-not-exist + (:ignore + (unless (sequence-exists-p name :database database) + (return-from drop-sequence))) + (:error t)) + (database-drop-sequence name database) + (values)) + +(defun list-sequences (&key (owner nil) (database *default-database*)) + "Returns a list of strings representing sequence names in +DATABASE which defaults to *DEFAULT-DATABASE*. OWNER is nil by +default which means that only sequences owned by users are +listed. If OWNER is a string denoting a user name, only sequences +owned by OWNER are listed. If OWNER is :all then all sequences +are listed." + (database-list-sequences database :owner owner)) + +(defun sequence-exists-p (name &key (owner nil) + (database *default-database*)) + "Tests for the existence of an SQL sequence called NAME in +DATABASE which defaults to *DEFAULT-DATABASE*. OWNER is nil by +default which means that only sequences owned by users are +examined. If OWNER is a string denoting a user name, only +sequences owned by OWNER are examined. If OWNER is :all then all +sequences are examined." + (let ((seqs (list-sequences :owner owner :database database)) + ;; handle symbols, we know the db will return strings + (n1 (database-identifier name database)) + (n2 (%sequence-name-to-table name database))) + (when (or (member n1 seqs :test #'database-identifier-equal) + (member n2 seqs :test #'database-identifier-equal)) + t))) + +(defun sequence-next (name &key (database *default-database*)) + "Increment and return the next value in the sequence called + NAME in DATABASE which defaults to *DEFAULT-DATABASE*." + (database-sequence-next (database-identifier name database) database)) + +(defun set-sequence-position (name position &key (database *default-database*)) + "Explicitly set the the position of the sequence called NAME in +DATABASE, which defaults to *DEFAULT-DATABASE*, to POSITION which +is returned." + (database-set-sequence-position (database-identifier name database) + position database)) + +(defun sequence-last (name &key (database *default-database*)) + "Return the last value allocated in the sequence called NAME in DATABASE + which defaults to *DEFAULT-DATABASE*." + (database-sequence-last (database-identifier name database) database)) + diff --git a/sql/fdml.lisp b/sql/fdml.lisp new file mode 100644 index 0000000..5e248ce --- /dev/null +++ b/sql/fdml.lisp @@ -0,0 +1,515 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; The CLSQL Functional Data Manipulation Language (FDML). +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +;; some helpers to make dealing with attribute-value-pairs a bit more structured +(defclass attribute-value-pair () + ((attribute :accessor attribute :initarg :attribute :initform nil) + (db-value :accessor db-value :initarg :db-value :initform nil)) + (:documentation "Represents an attribute-sql-expression and its value, used + to pass to insert/update. Was previously a two list")) + +(defun make-attribute-value-pair (slot-def value database) + (check-slot-type slot-def value) + (make-instance + 'attribute-value-pair + :attribute (sql-expression :attribute (database-identifier slot-def database)) + :db-value (db-value-from-slot slot-def value database))) + +(defun to-attributes-and-values (av-pairs) + (etypecase (first av-pairs) + (list + (loop for (a v) in av-pairs + collect a into attributes + collect v into db-values + finally (return (values attributes db-values)))) + (attribute-value-pair + (loop for pair in av-pairs + collecting (attribute pair) into attributes + collecting (db-value pair) into db-values + finally (return (values attributes db-values)))))) + +;;; Basic operations on databases + +(defmethod database-query-result-set ((expr %sql-expression) database + &key full-set result-types) + (database-query-result-set (sql-output expr database) database + :full-set full-set :result-types result-types)) + +(defmethod execute-command ((sql-expression string) + &key (database *default-database*)) + (record-sql-command sql-expression database) + (let ((res (database-execute-command sql-expression database))) + (record-sql-result res database)) + (values)) + +(defmethod execute-command ((expr %sql-expression) + &key (database *default-database*)) + (execute-command (sql-output expr database) :database database) + (values)) + +(defmethod query ((query-expression string) &key (database *default-database*) + (result-types :auto) (flatp nil) (field-names t)) + (record-sql-command query-expression database) + (multiple-value-bind (rows names) + (database-query query-expression database result-types field-names) + (let ((result (if (and flatp (= 1 (length (car rows)))) + (mapcar #'car rows) + rows))) + (record-sql-result result database) + (if field-names + (values result names) + result)))) + +(defmethod query ((expr %sql-expression) &key (database *default-database*) + (result-types :auto) (flatp nil) (field-names t)) + (query (sql-output expr database) :database database :flatp flatp + :result-types result-types :field-names field-names)) + +(defmethod query ((expr sql-object-query) &key (database *default-database*) + (result-types :auto) (flatp nil) (field-names t)) + (declare (ignore result-types field-names)) + (apply #'select (append (slot-value expr 'objects) + (slot-value expr 'exp) + (when (slot-value expr 'refresh) + (list :refresh (sql-output expr database))) + (when (or flatp (slot-value expr 'flatp) ) + (list :flatp t)) + (list :database database)))) + + +(defun print-query (query-exp &key titles (formats t) (sizes t) (stream t) + (database *default-database*)) + "Prints a tabular report of the results returned by the SQL +query QUERY-EXP, which may be a symbolic SQL expression or a +string, in DATABASE which defaults to *DEFAULT-DATABASE*. The +report is printed onto STREAM which has a default value of t +which means that *STANDARD-OUTPUT* is used. The TITLE argument, +which defaults to nil, allows the specification of a list of +strings to use as column titles in the tabular output. SIZES +accepts a list of column sizes, one for each column selected by +QUERY-EXP, to use in formatting the tabular report. The default +value of t means that minimum sizes are computed. FORMATS is a +list of format strings to be used for printing each column +selected by QUERY-EXP. The default value of FORMATS is t meaning +that ~A is used to format all columns or ~VA if column sizes are +used." + (flet ((compute-sizes (data) + (mapcar #'(lambda (x) + (apply #'max (mapcar #'(lambda (y) + (if (null y) 3 (length y))) + x))) + (apply #'mapcar (cons #'list data)))) + (format-record (record control sizes) + (format stream "~&~?" control + (if (null sizes) record + (mapcan #'(lambda (s f) (list s f)) sizes record))))) + (let* ((query-exp (etypecase query-exp + (string query-exp) + (sql-query (sql-output query-exp database)))) + (data (query query-exp :database database :result-types nil + :field-names nil)) + (sizes (if (or (null sizes) (listp sizes)) sizes + (compute-sizes (if titles (cons titles data) data)))) + (formats (if (or (null formats) (not (listp formats))) + (make-list (length (car data)) :initial-element + (if (null sizes) "~A " "~VA ")) + formats)) + (control-string (format nil "~{~A~}" formats))) + (when titles (format-record titles control-string sizes)) + (dolist (d data (values)) (format-record d control-string sizes))))) + +(defun insert-records (&key (into nil) + (attributes nil) + (values nil) + (av-pairs nil) + (query nil) + (database *default-database*)) + "Inserts records into the table specified by INTO in DATABASE +which defaults to *DEFAULT-DATABASE*. There are five ways of +specifying the values inserted into each row. + +In the first VALUES contains a list of values to insert and ATTRIBUTES, +AV-PAIRS and QUERY are nil. This can be used when values are supplied for all +attributes in INTO. + +In the second, ATTRIBUTES is a list of column names, VALUES is a corresponding +list of values and AV-PAIRS and QUERY are nil. + +In the third, ATTRIBUTES, VALUES and QUERY are nil and AV-PAIRS is a list +of (attribute value) pairs, or attribute-value-pair objects. + +In the fourth, VALUES, AV-PAIRS and ATTRIBUTES are nil and QUERY is a symbolic +SQL query expression in which the selected columns also exist in INTO. + +In the fifth method, VALUES and AV-PAIRS are nil and ATTRIBUTES is a list of +column names and QUERY is a symbolic SQL query expression which returns values +for the specified columns." + (let ((stmt (make-sql-insert :into into :attrs attributes + :vals values :av-pairs av-pairs + :subquery query))) + (execute-command stmt :database database))) + +(defun make-sql-insert (&key (into nil) + (attrs nil) + (vals nil) + (av-pairs nil) + (subquery nil)) + (unless into + (error 'sql-user-error :message ":into keyword not supplied")) + (let ((insert (make-instance 'sql-insert :into (database-identifier into nil)))) + (with-slots (attributes values query) insert + + (cond ((and vals (not attrs) (not query) (not av-pairs)) + (setf values vals)) + + ((and vals attrs (not subquery) (not av-pairs)) + (setf attributes attrs) + (setf values vals)) + + ((and av-pairs (not vals) (not attrs) (not subquery)) + (multiple-value-setq (attributes values) + (to-attributes-and-values av-pairs))) + + ((and subquery (not vals) (not attrs) (not av-pairs)) + (setf query subquery)) + + ((and subquery attrs (not vals) (not av-pairs)) + (setf attributes attrs) + (setf query subquery)) + + (t (error 'sql-user-error + :message "bad or ambiguous keyword combination."))) + insert))) + +(defun delete-records (&key (from nil) + (where nil) + (database *default-database*)) + "Deletes records satisfying the SQL expression WHERE from the +table specified by FROM in DATABASE specifies a database which +defaults to *DEFAULT-DATABASE*." + (let ((stmt (make-instance 'sql-delete :from (database-identifier from database) :where where))) + (execute-command stmt :database database))) + +(defun update-records (table &key (attributes nil) + (values nil) + (av-pairs nil) + (where nil) + (database *default-database*)) + "Updates the attribute values of existing records satsifying +the SQL expression WHERE in the table specified by TABLE in +DATABASE which defaults to *DEFAULT-DATABASE*. There are three +ways of specifying the values to update for each row. In the +first, VALUES contains a list of values to use in the update and +ATTRIBUTES and AV-PAIRS are nil. This can be used when values are +supplied for all attributes in TABLE. In the second, ATTRIBUTES +is a list of column names, VALUES is a corresponding list of +values and AV-PAIRS is nil. In the third, ATTRIBUTES and VALUES +are nil and AV-PAIRS is an alist of (attribute value) pairs." + (when av-pairs + (multiple-value-setq (attributes values) + (to-attributes-and-values av-pairs))) + (let ((stmt (make-instance 'sql-update :table (database-identifier table database) + :attributes attributes + :values values + :where where))) + (execute-command stmt :database database))) + + +;;; Iteration + +(defmacro do-query (((&rest args) query-expression + &key (database '*default-database*) (result-types :auto)) + &body body) + "Repeatedly executes BODY within a binding of ARGS on the +fields of each row selected by the SQL query QUERY-EXPRESSION, +which may be a string or a symbolic SQL expression, in DATABASE +which defaults to *DEFAULT-DATABASE*. The values returned by the +execution of BODY are returned. RESULT-TYPES is a list of symbols +which specifies the lisp type for each field returned by +QUERY-EXPRESSION. If RESULT-TYPES is nil all results are returned +as strings whereas the default value of :auto means that the lisp +types are automatically computed for each field." + (let ((result-set (gensym "RESULT-SET-")) + (qe (gensym "QUERY-EXPRESSION-")) + (columns (gensym "COLUMNS-")) + (row (gensym "ROW-")) + (db (gensym "DB-")) + (last-form-eval (gensym "LFE-"))) + `(let ((,qe ,query-expression) + (,db ,database)) + (typecase ,qe + (sql-object-query + (dolist (,row (query ,qe :database ,db)) + (destructuring-bind ,args + ,row + ,@body))) + (t + ;; Functional query + (multiple-value-bind (,result-set ,columns) + (database-query-result-set ,qe ,db + :full-set nil + :result-types ,result-types) + (when ,result-set + (unwind-protect + (do ((,row (make-list ,columns)) + (,last-form-eval nil)) + ((not (database-store-next-row ,result-set ,db ,row)) + ,last-form-eval) + (destructuring-bind ,args ,row + (setq ,last-form-eval + (progn + ,@body)))) + (database-dump-result-set ,result-set ,db))))))))) + +(defun map-query (output-type-spec function query-expression + &key (database *default-database*) + (result-types :auto)) + "Map the function FUNCTION over the attribute values of each +row selected by the SQL query QUERY-EXPRESSION, which may be a +string or a symbolic SQL expression, in DATABASE which defaults +to *DEFAULT-DATABASE*. The results of the function are collected +as specified in OUTPUT-TYPE-SPEC and returned like in +MAP. RESULT-TYPES is a list of symbols which specifies the lisp +type for each field returned by QUERY-EXPRESSION. If RESULT-TYPES +is nil all results are returned as strings whereas the default +value of :auto means that the lisp types are automatically +computed for each field." + (typecase query-expression + (sql-object-query + (map output-type-spec #'(lambda (x) (apply function x)) + (query query-expression))) + (t + ;; Functional query + (macrolet ((type-specifier-atom (type) + `(if (atom ,type) ,type (car ,type)))) + (case (type-specifier-atom output-type-spec) + ((nil) + (map-query-for-effect function query-expression database + result-types)) + (list + (map-query-to-list function query-expression database result-types)) + ((simple-vector simple-string vector string array simple-array + bit-vector simple-bit-vector base-string + simple-base-string) + (map-query-to-simple output-type-spec function query-expression + database result-types)) + (t + (funcall #'map-query + (cmucl-compat:result-type-or-lose output-type-spec t) + function query-expression :database database + :result-types result-types))))))) + +(defun map-query-for-effect (function query-expression database result-types) + (multiple-value-bind (result-set columns) + (database-query-result-set query-expression database :full-set nil + :result-types result-types) + (let ((flatp (and (= columns 1) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) + (when result-set + (unwind-protect + (do ((row (make-list columns))) + ((not (database-store-next-row result-set database row)) + nil) + (if flatp + (apply function row) + (funcall function row))) + (database-dump-result-set result-set database)))))) + +(defun map-query-to-list (function query-expression database result-types) + (multiple-value-bind (result-set columns) + (database-query-result-set query-expression database :full-set nil + :result-types result-types) + (let ((flatp (and (= columns 1) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) + (when result-set + (unwind-protect + (let ((result (list nil))) + (do ((row (make-list columns)) + (current-cons result (cdr current-cons))) + ((not (database-store-next-row result-set database row)) + (cdr result)) + (rplacd current-cons + (list (if flatp + (apply function row) + (funcall function (copy-list row))))))) + (database-dump-result-set result-set database)))))) + +(defun map-query-to-simple (output-type-spec function query-expression database result-types) + (multiple-value-bind (result-set columns rows) + (database-query-result-set query-expression database :full-set t + :result-types result-types) + (let ((flatp (and (= columns 1) + (typep query-expression 'sql-query) + (slot-value query-expression 'flatp)))) + (when result-set + (unwind-protect + (if rows + ;; We know the row count in advance, so we allocate once + (do ((result + (cmucl-compat:make-sequence-of-type output-type-spec rows)) + (row (make-list columns)) + (index 0 (1+ index))) + ((not (database-store-next-row result-set database row)) + result) + (declare (fixnum index)) + (setf (aref result index) + (if flatp + (apply function row) + (funcall function (copy-list row))))) + ;; Database can't report row count in advance, so we have + ;; to grow and shrink our vector dynamically + (do ((result + (cmucl-compat:make-sequence-of-type output-type-spec 100)) + (allocated-length 100) + (row (make-list columns)) + (index 0 (1+ index))) + ((not (database-store-next-row result-set database row)) + (cmucl-compat:shrink-vector result index)) + (declare (fixnum allocated-length index)) + (when (>= index allocated-length) + (setq allocated-length (* allocated-length 2) + result (adjust-array result allocated-length))) + (setf (aref result index) + (if flatp + (apply function row) + (funcall function (copy-list row)))))) + (database-dump-result-set result-set database)))))) + +;;; Row processing macro from CLSQL + +(defmacro for-each-row (((&rest fields) &key from order-by where distinct limit) + &body body) + (let ((d (gensym "DISTINCT-")) + (bind-fields (loop for f in fields collect (car f))) + (w (gensym "WHERE-")) + (o (gensym "ORDER-BY-")) + (frm (gensym "FROM-")) + (l (gensym "LIMIT-")) + (q (gensym "QUERY-"))) + `(let ((,frm ,from) + (,w ,where) + (,d ,distinct) + (,l ,limit) + (,o ,order-by)) + (let ((,q (query-string ',fields ,frm ,w ,d ,o ,l))) + (loop for tuple in (query ,q) + collect (destructuring-bind ,bind-fields tuple + ,@body)))))) + +(defun query-string (fields from where distinct order-by limit) + (concatenate + 'string + (format nil "select ~A~{~A~^,~} from ~{~A~^ and ~}" + (if distinct "distinct " "") (field-names fields) + (from-names from)) + (if where (format nil " where ~{~A~^ ~}" + (where-strings where)) "") + (if order-by (format nil " order by ~{~A~^, ~}" + (order-by-strings order-by))) + (if limit (format nil " limit ~D" limit) ""))) + +(defun lisp->sql-name (field) + (typecase field + (string field) + (symbol (string-upcase (symbol-name field))) + (cons (cadr field)) + (t (format nil "~A" field)))) + +(defun field-names (field-forms) + "Return a list of field name strings from a fields form" + (loop for field-form in field-forms + collect + (lisp->sql-name + (if (cadr field-form) + (cadr field-form) + (car field-form))))) + +(defun from-names (from) + "Return a list of field name strings from a fields form" + (loop for table in (if (atom from) (list from) from) + collect (lisp->sql-name table))) + + +(defun where-strings (where) + (loop for w in (if (atom (car where)) (list where) where) + collect + (if (consp w) + (format nil "~A ~A ~A" (second w) (first w) (third w)) + (format nil "~A" w)))) + +(defun order-by-strings (order-by) + (loop for o in order-by + collect + (if (atom o) + (lisp->sql-name o) + (format nil "~A ~A" (lisp->sql-name (car o)) + (lisp->sql-name (cadr o)))))) + + +;;; Large objects support + +(defun create-large-object (&key (database *default-database*)) + "Creates a new large object in the database and returns the object identifier" + (database-create-large-object database)) + +(defun write-large-object (object-id data &key (database *default-database*)) + "Writes data to the large object" + (database-write-large-object object-id data database)) + +(defun read-large-object (object-id &key (database *default-database*)) + "Reads the large object content" + (database-read-large-object object-id database)) + +(defun delete-large-object (object-id &key (database *default-database*)) + "Deletes the large object in the database" + (database-delete-large-object object-id database)) + + +;;; Prepared statements + +(defun prepare-sql (sql-stmt types &key (database *default-database*) (result-types :auto) field-names) + "Prepares a SQL statement for execution. TYPES contains a +list of types corresponding to the input parameters. Returns a +prepared-statement object. + +A type can be + :int + :double + :null + (:blob n) + (:string n) +" + (unless (db-type-has-prepared-stmt? (database-type database)) + (error 'sql-user-error + :message + (format nil + "Database backend type ~:@(~A~) does not support prepared statements." + (database-type database)))) + + (database-prepare sql-stmt types database result-types field-names)) + +(defun bind-parameter (prepared-stmt position value) + "Sets the value of a parameter is in prepared statement." + (database-bind-parameter prepared-stmt position value) + value) + +(defun run-prepared-sql (prepared-stmt) + "Execute the prepared sql statment. All input parameters must be bound." + (database-run-prepared prepared-stmt)) + +(defun free-prepared-sql (prepared-stmt) + "Delete the objects associated with a prepared statement." + (database-free-prepared prepared-stmt)) diff --git a/sql/generic-odbc.lisp b/sql/generic-odbc.lisp new file mode 100644 index 0000000..706e4cf --- /dev/null +++ b/sql/generic-odbc.lisp @@ -0,0 +1,263 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Generic ODBC layer, used by db-odbc and db-aodbc backends +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defclass generic-odbc-database (database) + ((dbi-package :initarg :dbi-package :reader dbi-package) + (odbc-conn :initarg :odbc-conn :initform nil :accessor odbc-conn) + (disconnect-fn :reader disconnect-fn) + (sql-fn :reader sql-fn) + (close-query-fn :reader close-query-fn) + (fetch-row :reader fetch-row-fn) + (list-all-database-tables-fn :reader list-all-database-tables-fn) + (list-all-table-columns-fn :reader list-all-table-columns-fn) + (odbc-db-type :accessor database-odbc-db-type :initarg :odbc-db-type )) + (:documentation "Encapsulate same behavior across odbc and aodbc backends.")) + +(defmethod initialize-instance :after ((db generic-odbc-database) + &rest all-keys) + (declare (ignore all-keys)) + (unless (slot-boundp db 'dbi-package) + (error "dbi-package not specified.")) + (let ((pkg (slot-value db 'dbi-package))) + (unless pkg + (error "dbi-package is nil.")) + (setf (slot-value db 'disconnect-fn) + (intern (symbol-name '#:disconnect) pkg) + (slot-value db 'sql-fn) + (intern (symbol-name '#:sql) pkg) + (slot-value db 'close-query-fn) + (intern (symbol-name '#:close-query) pkg) + (slot-value db 'fetch-row) + (intern (symbol-name '#:fetch-row) pkg) + (slot-value db 'list-all-database-tables-fn) + (intern (symbol-name '#:list-all-database-tables) pkg) + (slot-value db 'list-all-table-columns-fn) + (intern (symbol-name '#:list-all-table-columns) pkg)))) + +;;; Type methods + +(defmethod database-get-type-specifier ((type symbol) args database + (db-type (eql :mssql))) + "Special database types for MSSQL backends" + (declare (ignore database db-type args)) + (case type + (wall-time "DATETIME") + (date "SMALLDATETIME") + ((generalized-boolean boolean) "BIT") + ((longchar text) "ntext") + ((varchar string) + (if args + (format nil "NVARCHAR(~A)" (car args)) + (format nil "NVARCHAR(~D)" *default-string-length*))) + (t (call-next-method)))) + +;;; Generation of SQL strings from lisp expressions + +(defmethod database-output-sql ((tee (eql t)) (database generic-odbc-database)) + (case (database-underlying-type database) + (:mssql "1") + (t "'Y'"))) + +;;; Database backend capabilities + +(defmethod db-type-use-fully-qualified-column-on-drop-index? ((db-type (eql :mssql))) + t) + +(defmethod db-type-has-boolean-where? ((db-type (eql :mssql))) + nil) + +(defmethod db-type-has-intersect? ((db-type (eql :mssql))) + nil) + +(defmethod db-type-has-except? ((db-type (eql :mssql))) + nil) + +;;; Backend methods + +(defmethod database-disconnect ((database generic-odbc-database)) + (funcall (disconnect-fn database) (odbc-conn database)) + (setf (odbc-conn database) nil) + t) + +(defmethod database-query (query-expression (database generic-odbc-database) + result-types field-names) + (handler-case + (funcall (sql-fn database) + query-expression :db (odbc-conn database) + :result-types result-types + :column-names field-names) + #+ignore + (error () + (error 'sql-database-data-error + :database database + :expression query-expression + :message "Query failed")))) + + +(defmethod database-execute-command (sql-expression (database generic-odbc-database)) + (handler-case + (funcall (sql-fn database) + sql-expression :db (odbc-conn database)) + #+ignore + (sql-error (e) + (error e)) + #+ignore + (error () + (error 'sql-database-data-error + :database database + :expression sql-expression + :message "Execute command failed")))) + + +(defstruct odbc-result-set + (query nil) + (types nil) + (full-set nil :type boolean)) + + + + +(defmethod database-query-result-set ((query-expression string) + (database generic-odbc-database) + &key full-set result-types) + (handler-case + (multiple-value-bind (query column-names) + (funcall (sql-fn database) + query-expression + :db (odbc-conn database) + :row-count nil + :column-names t + :query t + :result-types result-types) + (values + (make-odbc-result-set :query query :full-set full-set + :types result-types) + (length column-names) + nil ;; not able to return number of rows with odbc + )) + (error () + (error 'sql-database-data-error + :database database + :expression query-expression + :message "Query result set failed")))) + +(defmethod database-dump-result-set (result-set (database generic-odbc-database)) + (funcall (close-query-fn database) (odbc-result-set-query result-set)) + t) + +(defmethod database-store-next-row (result-set + (database generic-odbc-database) + list) + (let ((row (funcall (fetch-row-fn database) + (odbc-result-set-query result-set) nil 'eof))) + (if (eq row 'eof) + nil + (progn + (loop for elem in row + for rest on list + do + (setf (car rest) elem)) + list)))) + + +(defun %database-list-* (database type owner) + "Internal function used by database-list-tables and +database-list-views" + (multiple-value-bind (rows col-names) + (funcall (list-all-database-tables-fn database) :db (odbc-conn database)) + (declare (ignore col-names)) + ;; http://msdn.microsoft.com/en-us/library/ms711831%28VS.85%29.aspx + ;; TABLE_SCHEM is hard-coded in second column by ODBC Driver Manager + ;; TABLE_NAME in third column, TABLE_TYPE in fourth column + (loop for (category schema name ttype . rest) in rows + when (and (string-equal type ttype) + (or (null owner) (string-equal owner schema)) + ;; unless requesting by name, skip system schema + (not (and (null owner) + (member schema '("information_schema" "sys") + :test #'string-equal))) + ;; skip system specific tables in mssql2000 + (not (and (eql :mssql (database-underlying-type database)) + (member name '("dtproperties" "sysconstraints" + "syssegments") + :test #'string-equal)))) + collect name))) + +(defmethod database-list-tables ((database generic-odbc-database) + &key (owner nil)) + "Since ODBC doesn't expose the owner we use that parameter to filter +on schema since that's what tends to be exposed. Some DBs like mssql +2000 conflate the two so at least there it works nicely." + (%database-list-* database "TABLE" owner)) + + +(defmethod database-list-views ((database generic-odbc-database) + &key (owner nil)) + "Since ODBC doesn't expose the owner we use that parameter to filter +on schema since that's what tends to be exposed. Some DBs like mssql +2000 conflate the two so at least there it works nicely." + (%database-list-* database "VIEW" owner)) + + +(defmethod database-list-attributes ((table %database-identifier) (database generic-odbc-database) + &key (owner nil) + &aux (table (unescaped-database-identifier table))) + (declare (ignore owner)) + (multiple-value-bind (rows col-names) + (funcall (list-all-table-columns-fn database) table + :db (odbc-conn database)) + (declare (ignore col-names)) + ;; COLUMN_NAME is hard-coded by odbc spec as fourth position + (loop for row in rows + collect (fourth row)))) + +(defmethod database-attribute-type ((attribute %database-identifier) (table %database-identifier) + (database generic-odbc-database) + &key (owner nil) + &aux (table (unescaped-database-identifier table)) + (attribute (unescaped-database-identifier attribute))) + (declare (ignore owner)) + (multiple-value-bind (rows col-names) + (funcall (list-all-table-columns-fn database) table + :db (odbc-conn database)) + (declare (ignore col-names)) + ;; COLUMN_NAME is hard-coded by odbc spec as fourth position + ;; TYPE_NAME is the sixth column + ;; PRECISION/COLUMN_SIZE is the seventh column + ;; SCALE/DECIMAL_DIGITS is the ninth column + ;; NULLABLE is the eleventh column + (loop for row in rows + when (string-equal attribute (fourth row)) + do + (let ((size (seventh row)) + (precision (ninth row)) + (scale (nth 10 row))) + (return (values (ensure-keyword (sixth row)) + (when size (parse-integer size)) + (when precision (parse-integer precision)) + (when scale (parse-integer scale)))))))) + +(defmethod database-last-auto-increment-id + ((database generic-odbc-database) table column) + (case (database-underlying-type database) + (:mssql + (first (clsql:query "SELECT SCOPE_IDENTITY()" + :flatp t + :database database + :result-types '(:int)))) + (t (if (next-method-p) + (call-next-method))))) + +(defmethod clsql-sys:db-type-has-auto-increment? ((db-underlying-type (eql :mssql))) + t) diff --git a/sql/generic-postgresql.lisp b/sql/generic-postgresql.lisp new file mode 100644 index 0000000..13d4f77 --- /dev/null +++ b/sql/generic-postgresql.lisp @@ -0,0 +1,429 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Generic postgresql layer, used by db-postgresql and db-postgresql-socket +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defclass generic-postgresql-database (database) + ((has-table-pg_roles :type boolean :reader has-table-pg_roles :initform nil)) + (:documentation "Encapsulate same behavior across postgresql and postgresql-socket backends.")) + + + +;; Object functions + +(defmethod database-get-type-specifier ((type symbol) args database + (db-type (eql :postgresql))) + "Special database types for POSTGRESQL backends" + (declare (ignore database db-type)) + (case type + (wall-time ;; TODO: why is this WITHOUT... + "TIMESTAMP WITHOUT TIME ZONE") + (string + ;; TODO: the default to CHAR here seems specious as the PG docs claim + ;; that char is slower than varchar + (if args + (format nil "CHAR(~A)" (car args)) + "VARCHAR")) + (number + (cond + ((and (consp args) (= (length args) 2)) + (format nil "NUMERIC(~D,~D)" (first args) (second args))) + ((and (consp args) (= (length args) 1)) + (format nil "NUMERIC(~D)" (first args))) + (t "NUMERIC"))) + ((tinyint smallint) "INT2") + (t (call-next-method)))) + +;;; Backend functions + +(defun owner-clause (owner) + (cond + ((stringp owner) + (format + nil + " AND (relowner=(SELECT usesysid FROM pg_user WHERE (usename='~A')))" + owner)) + ((null owner) + (format nil " AND (relowner<>(SELECT usesysid FROM pg_user WHERE usename='postgres'))")) + (t ""))) + +(defun has-table (name database) + (let ((name-retrieved + (caar (database-query + (format nil "SELECT relname FROM pg_class WHERE relname='~A'" + name) + database nil nil)))) + (if (and (stringp name-retrieved) (plusp (length name-retrieved))) + t + nil))) + +(defmethod slot-unbound (class (obj generic-postgresql-database) + (slot (eql 'has-table-pg_roles))) + ;; Lazily cache slot value + (declare (ignore class)) + (setf (slot-value obj 'has-table-pg_roles) (has-table "pg_roles" obj))) + +(defun database-list-objects-of-type (database type owner) + (mapcar #'car + (database-query + (format nil + (if (and (has-table-pg_roles database) + (not (eq owner :all))) + " + SELECT c.relname + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_roles r ON r.oid = c.relowner + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('~A','') + AND n.nspname NOT IN ('pg_catalog', 'pg_toast') + AND pg_catalog.pg_table_is_visible(c.oid) + ~A" + "SELECT relname FROM pg_class WHERE (relkind = +'~A')~A") + type + (owner-clause owner)) + database nil nil))) + +(defmethod database-list-tables ((database generic-postgresql-database) + &key (owner nil)) + (database-list-objects-of-type database "r" owner)) + +(defmethod database-list-views ((database generic-postgresql-database) + &key (owner nil)) + (database-list-objects-of-type database "v" owner)) + +(defmethod database-list-indexes ((database generic-postgresql-database) + &key (owner nil)) + (database-list-objects-of-type database "i" owner)) + + +(defmethod database-list-table-indexes (table (database generic-postgresql-database) + &key (owner nil)) + (let ((indexrelids + (database-query + (format + nil + "select indexrelid from pg_index where indrelid=(select relfilenode from pg_class where LOWER(relname)='~A'~A)" + (string-downcase (unescaped-database-identifier table)) + (owner-clause owner)) + database :auto nil)) + (result nil)) + (dolist (indexrelid indexrelids (nreverse result)) + (push + (caar (database-query + (format nil "select relname from pg_class where relfilenode='~A'" + (car indexrelid)) + database nil nil)) + result)))) + +(defmethod database-list-attributes ((table %database-identifier) + (database generic-postgresql-database) + &key (owner nil)) + (let* ((table (unescaped-database-identifier table)) + (owner-clause + (cond ((stringp owner) + (format nil " AND (relowner=(SELECT usesysid FROM pg_user WHERE usename='~A'))" owner)) + ((null owner) " AND (not (relowner=1))") + (t ""))) + (result + (mapcar #'car + (database-query + (format nil "SELECT attname FROM pg_class,pg_attribute WHERE pg_class.oid=attrelid AND attisdropped = FALSE AND relname='~A'~A" + (string-downcase table) + owner-clause) + database nil nil)))) + (if result + (remove-if #'(lambda (it) (member it '("cmin" + "cmax" + "xmax" + "xmin" + "oid" + "ctid" + ;; kmr -- added tableoid + "tableoid") :test #'equal)) + result)))) + +(defmethod database-attribute-type ((attribute %database-identifier) + (table %database-identifier) + (database generic-postgresql-database) + &key (owner nil) + &aux (table (unescaped-database-identifier table)) + (attribute (unescaped-database-identifier attribute))) + (let ((row (car (database-query + (format nil "SELECT pg_type.typname,pg_attribute.attlen,pg_attribute.atttypmod,pg_attribute.attnotnull FROM pg_type,pg_class,pg_attribute WHERE pg_class.oid=pg_attribute.attrelid AND pg_class.relname='~A' AND pg_attribute.attname='~A' AND pg_attribute.atttypid=pg_type.oid~A" + (string-downcase table) + (string-downcase attribute) + (owner-clause owner)) + database nil nil)))) + (when row + (destructuring-bind (typname attlen atttypmod attnull) row + (setf attlen (%get-int attlen) + atttypmod (%get-int atttypmod)) + (let ((coltype (ensure-keyword typname)) + (colnull (typecase attnull + (string (if (string-equal "f" attnull) 1 0)) + (null 1) + (T 0))) + collen + colprec) + (setf (values collen colprec) + (case coltype + ((:numeric :decimal) + (if (= -1 atttypmod) + (values nil nil) + (values (ash (- atttypmod 4) -16) + (boole boole-and (- atttypmod 4) #xffff)))) + (otherwise + (values + (cond ((and (= -1 attlen) (= -1 atttypmod)) nil) + ((= -1 attlen) (- atttypmod 4)) + (t attlen)) + nil)))) + (values coltype collen colprec colnull)))))) + +(defmethod database-create-sequence (sequence-name + (database generic-postgresql-database)) + (let ((cmd (concatenate + 'string "CREATE SEQUENCE " (escaped-database-identifier sequence-name database)))) + (database-execute-command cmd database))) + +(defmethod database-drop-sequence (sequence-name + (database generic-postgresql-database)) + (database-execute-command + (concatenate 'string "DROP SEQUENCE " (escaped-database-identifier sequence-name database)) + database)) + +(defmethod database-list-sequences ((database generic-postgresql-database) + &key (owner nil)) + (database-list-objects-of-type database "S" owner)) + +(defmethod database-set-sequence-position (name (position integer) + (database generic-postgresql-database)) + (values + (%get-int + (caar + (database-query + (format nil "SELECT SETVAL ('~A', ~A)" (escaped-database-identifier name) position) + database nil nil))))) + +(defmethod database-sequence-next (sequence-name + (database generic-postgresql-database)) + (values + (%get-int + (caar + (database-query + (concatenate 'string "SELECT NEXTVAL ('" (escaped-database-identifier sequence-name) "')") + database nil nil))))) + +(defmethod database-sequence-last (sequence-name (database generic-postgresql-database)) + (values + (%get-int + (caar + (database-query + (concatenate 'string "SELECT LAST_VALUE FROM " (escaped-database-identifier sequence-name)) + database nil nil))))) + +(defmethod auto-increment-sequence-name (table column (database generic-postgresql-database)) + (let* ((sequence-name (or (database-identifier (slot-value column 'autoincrement-sequence)) + (combine-database-identifiers + (list table column 'seq) + database)))) + (when (search "'" (escaped-database-identifier sequence-name) + :test #'string-equal) + (signal-database-too-strange + "PG Sequence names shouldnt contain single quotes for the sake of sanity")) + sequence-name)) + +(defmethod database-last-auto-increment-id ((database generic-postgresql-database) table column) + (let ((seq-name (auto-increment-sequence-name table column database))) + (first (clsql:query (format nil "SELECT currval ('~a')" + (escaped-database-identifier seq-name)) + :flatp t + :database database + :result-types '(:int))))) + +(defmethod database-generate-column-definition + (class slotdef (database generic-postgresql-database)) + (when (member (view-class-slot-db-kind slotdef) '(:base :key)) + (let ((cdef + (list (sql-expression :attribute (database-identifier slotdef database)) + (specified-type slotdef) + (view-class-slot-db-type slotdef))) + (const (listify (view-class-slot-db-constraints slotdef))) + (seq (auto-increment-sequence-name class slotdef database))) + (when seq + (setf const (remove :auto-increment const)) + (unless (member :default const) + (let* ((next (format nil " nextval('~a')" (escaped-database-identifier seq)))) + (setf const (append const (list :default next)))))) + (append cdef const)))) + +(defmethod database-add-autoincrement-sequence + ((self standard-db-class) (database generic-postgresql-database)) + (let ((ordered-slots (slots-for-possibly-normalized-class self))) + (dolist (slotdef ordered-slots) + ;; ensure that referenceed sequences actually exist before referencing them + (let ((sequence-name (auto-increment-sequence-name self slotdef database))) + (when (and sequence-name + (not (sequence-exists-p sequence-name :database database))) + (create-sequence sequence-name :database database)))))) + +(defmethod database-remove-autoincrement-sequence + ((table standard-db-class) + (database generic-postgresql-database)) + (let ((ordered-slots (slots-for-possibly-normalized-class table))) + (dolist (slotdef ordered-slots) + ;; ensure that referenceed sequences are dropped with the table + (let ((sequence-name (auto-increment-sequence-name table slotdef database))) + (when sequence-name (drop-sequence sequence-name)))))) + +(defun postgresql-database-list (connection-spec type) + (destructuring-bind (host name &rest other-args) connection-spec + (declare (ignore name)) + (let ((database (database-connect (list* host "template1" other-args) + type))) + (unwind-protect + (progn + (setf (slot-value database 'clsql-sys::state) :open) + (mapcar #'car (database-query "select datname from pg_database" + database nil nil))) + (progn + (database-disconnect database) + (setf (slot-value database 'clsql-sys::state) :closed)))))) + +(defmethod database-list (connection-spec (type (eql :postgresql))) + (postgresql-database-list connection-spec type)) + +(defmethod database-list (connection-spec (type (eql :postgresql-socket))) + (postgresql-database-list connection-spec type)) + +#+nil +(defmethod database-describe-table ((database generic-postgresql-database) table) + ;; MTP: LIST-ATTRIBUTE-TYPES currently executes separate queries for + ;; each attribute. It would be more efficient to have a single SQL + ;; query return the type data for all attributes. This code is + ;; retained as an example of how to do this for PostgreSQL. + (database-query + (format nil "select a.attname, t.typname + from pg_class c, pg_attribute a, pg_type t + where c.relname = '~a' + and a.attnum > 0 + and a.attrelid = c.oid + and a.atttypid = t.oid" + (sql-escape (string-downcase table))) + database :auto nil)) + +;;; Prepared statements + +(defvar *next-prepared-id-num* 0) +(defun next-prepared-id () + (let ((num (incf *next-prepared-id-num*))) + (format nil "CLSQL_PS_~D" num))) + +(defclass postgresql-stmt () + ((database :initarg :database :reader database) + (id :initarg :id :reader id) + (bindings :initarg :bindings :reader bindings) + (field-names :initarg :field-names :accessor stmt-field-names) + (result-types :initarg :result-types :reader result-types))) + +(defun clsql-type->postgresql-type (type) + (cond + ((in type :int :integer) "INT4") + ((in type :short) "INT2") + ((in type :bigint) "INT8") + ((in type :float :double :number) "NUMERIC") + ((and (consp type) (in (car type) :char :varchar)) "VARCHAR") + (t + (error 'sql-user-error + :message + (format nil "Unknown clsql type ~A." type))))) + +(defun prepared-sql-to-postgresql-sql (sql) + ;; FIXME: Convert #\? to "$n". Don't convert within strings + (declare (simple-string sql)) + (with-output-to-string (out) + (do ((len (length sql)) + (param 0) + (in-str nil) + (pos 0 (1+ pos))) + ((= len pos)) + (declare (fixnum len param pos)) + (let ((c (schar sql pos))) + (declare (character c)) + (cond + ((or (char= c #\") (char= c #\')) + (setq in-str (not in-str)) + (write-char c out)) + ((and (char= c #\?) (not in-str)) + (write-char #\$ out) + (write-string (write-to-string (incf param)) out)) + (t + (write-char c out))))))) + +(defmethod database-prepare (sql-stmt types (database generic-postgresql-database) result-types field-names) + (let ((id (next-prepared-id))) + (database-execute-command + (format nil "PREPARE ~A (~{~A~^,~}) AS ~A" + id + (mapcar #'clsql-type->postgresql-type types) + (prepared-sql-to-postgresql-sql sql-stmt)) + database) + (make-instance 'postgresql-stmt + :id id + :database database + :result-types result-types + :field-names field-names + :bindings (make-list (length types))))) + +(defmethod database-bind-parameter ((stmt postgresql-stmt) position value) + (setf (nth (1- position) (bindings stmt)) value)) + +(defun binding-to-param (binding) + (typecase binding + (string + (concatenate 'string "'" (sql-escape-quotes binding) "'")) + (t + binding))) + +(defmethod database-run-prepared ((stmt postgresql-stmt)) + (with-slots (database id bindings field-names result-types) stmt + (let ((query (format nil "EXECUTE ~A (~{~A~^,~})" + id (mapcar #'binding-to-param bindings)))) + (cond + ((and field-names (not (consp field-names))) + (multiple-value-bind (res names) + (database-query query database result-types field-names) + (setf field-names names) + (values res names))) + (field-names + (values (nth-value 0 (database-query query database result-types nil)) + field-names)) + (t + (database-query query database result-types field-names)))))) + +;;; Capabilities + +(defmethod db-type-has-fancy-math? ((db-type (eql :postgresql))) + t) + +(defmethod db-type-default-case ((db-type (eql :postgresql))) + :lower) + +(defmethod db-type-has-prepared-stmt? ((db-type (eql :postgresql))) + t) + +(defmethod db-type-has-prepared-stmt? ((db-type (eql :postgresql-socket))) + t) + +(defmethod db-type-has-auto-increment? ((db-type (eql :postgresql))) + t) diff --git a/sql/generics.lisp b/sql/generics.lisp new file mode 100644 index 0000000..6ca064a --- /dev/null +++ b/sql/generics.lisp @@ -0,0 +1,212 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: generics.lisp +;;;; Purpose: Generic function definitions for DB interfaces +;;;; Author: Kevin M. Rosenberg +;;;; Created: Apr 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + + +;; FDML +(defgeneric choose-database-for-instance (object &optional database) + (:documentation "Used by the oodml functions to select which + database object to use. Chooses the database associated with the + object primarily, falls back to the database provided as an argument + or the *DEFAULT-DATABASE*.")) + +(defgeneric execute-command (expression &key database) + (:documentation + "Executes the SQL command EXPRESSION, which may be an SQL +expression or a string representing any SQL statement apart from +a query, on the supplied DATABASE which defaults to +*DEFAULT-DATABASE*.")) + + +(defgeneric query (query-expression &key database result-types flatp field-names) + (:documentation + "Executes the SQL query expression QUERY-EXPRESSION, which may +be an SQL expression or a string, on the supplied DATABASE which +defaults to *DEFAULT-DATABASE*. RESULT-TYPES is a list of symbols +which specifies the lisp type for each field returned by +QUERY-EXPRESSION. If RESULT-TYPES is nil all results are returned +as strings whereas the default value of :auto means that the lisp +types are automatically computed for each field. FIELD-NAMES is t +by default which means that the second value returned is a list +of strings representing the columns selected by +QUERY-EXPRESSION. If FIELD-NAMES is nil, the list of column names +is not returned as a second value. FLATP has a default value of +nil which means that the results are returned as a list of +lists. If FLATP is t and only one result is returned for each +record selected by QUERY-EXPRESSION, the results are returned as +elements of a list.")) + + +;; OODML + +(defgeneric update-record-from-slot (object slot &key database) + (:documentation + "Updates the value stored in the column represented by the +slot, specified by the CLOS slot name SLOT, of View Class +instance OBJECT. DATABASE defaults to *DEFAULT-DATABASE* and +specifies the database in which the update is made only if OBJECT +is not associated with a database. In this case, a record is +created in DATABASE and the attribute represented by SLOT is +initialised from the value of the supplied slots with other +attributes having default values. Furthermore, OBJECT becomes +associated with DATABASE.")) + +(defgeneric update-record-from-slots (object slots &key database) + (:documentation + "Updates the values stored in the columns represented by the +slots, specified by the CLOS slot names SLOTS, of View Class +instance OBJECT. DATABASE defaults to *DEFAULT-DATABASE* and +specifies the database in which the update is made only if OBJECT +is not associated with a database. In this case, a record is +created in the appropriate table of DATABASE and the attributes +represented by SLOTS are initialised from the values of the +supplied slots with other attributes having default +values. Furthermore, OBJECT becomes associated with DATABASE.")) + +(defgeneric update-records-from-instance (object &key database) + (:documentation + "Using an instance of a View Class, OBJECT, update the table +that stores its instance data. DATABASE defaults to +*DEFAULT-DATABASE* and specifies the database in which the update +is made only if OBJECT is not associated with a database. In this +case, a record is created in the appropriate table of DATABASE +using values from the slot values of OBJECT, and OBJECT becomes +associated with DATABASE.")) + +(defgeneric delete-instance-records (object &key database) + (:documentation + "Deletes the records represented by OBJECT in the appropriate +table of the database associated with OBJECT. If OBJECT is not +yet associated with a database, an error is signalled.")) + +(defgeneric update-instance-from-records (object &key database) + (:documentation + "Updates the slot values of the View Class instance OBJECT +using the attribute values of the appropriate table of DATABASE +which defaults to the database associated with OBJECT or, if +OBJECT is not associated with a database, *DEFAULT-DATABASE*. +Join slots are updated but instances of the class on which the +join is made are not updated.")) + +(defgeneric update-slot-from-record (object slot &key database) + (:documentation + "Updates the slot value, specified by the CLOS slot name SLOT, +of the View Class instance OBJECT using the attribute values of +the appropriate table of DATABASE which defaults to the database +associated with OBJECT or, if OBJECT is not associated with a +database, *DEFAULT-DATABASE*. Join slots are updated but +instances of the class on which the join is made are not +updated.")) + +(defgeneric instance-refreshed (object) + (:documentation + "Provides a hook which is called within an object oriented +call to SELECT with a non-nil value of REFRESH when the View +Class instance OBJECT has been updated from the database. A +method specialised on STANDARD-DB-OBJECT is provided which has no +effects. Methods specialised on particular View Classes can be +used to specify any operations that need to be made on View +Classes instances which have been updated in calls to SELECT.")) + +(defgeneric update-slot-with-null (instance slotdef) + (:documentation "Called to update a slot when its column has a NULL +value. If nulls are allowed for the column, the slot's value will be +nil, otherwise its value will be set to the result of calling +DATABASE-NULL-VALUE on the type of the slot.")) + +(defgeneric database-pkey-constraint (class database) + ) +(defgeneric %install-class (class database &key transactions) + ) +(defgeneric database-generate-column-definition (class slotdef database) + ) +(defgeneric update-slot-from-db (instance slotdef val) + ) +(defgeneric key-value-from-db (slotdef value database) + ) +(defgeneric get-slot-values-from-view (obj slotdeflist values) + ) +(defgeneric database-output-sql-as-type (type val database db-type) + ) +(defgeneric read-sql-value (val type database db-type) + ) +(defgeneric database-add-autoincrement-sequence (class database) + (:method (class database) nil) + (:documentation "If a database needs to add a sequence for its + autoincrement to work, this is where it should go. Default is + that it doesnt so just return nil")) +(defgeneric database-remove-autoincrement-sequence (class database) + (:method (class database) nil) + (:documentation "If a database needs to add a sequence for its + autoincrement to work, this is where it should go. Default is + that it doesnt so just return nil")) +(defgeneric auto-increment-sequence-name (class slotdef database) + (:documentation "The sequence name to create for this autoincremnt column on this class + if returns nil, there is no associated sequence ")) + +(defmethod auto-increment-sequence-name :around (class slot database) + (when (auto-increment-column-p slot database) + (call-next-method))) + +(defgeneric database-last-auto-increment-id (database table column) + ) + + + +;; Generation of SQL strings from lisp expressions + +(defgeneric output-sql (expr database) + (:documentation "Writes an SQL string appropriate for DATABASE + and corresponding to the lisp expression EXPR to + *SQL-STREAM*. The function SQL-OUTPUT is a top-level call for + generating SQL strings which initialises *SQL-STREAM*, calls + OUTPUT-SQL and reads the generated SQL string from + *SQL-STREAM*.")) + +(defgeneric database-output-sql (expr database) + (:documentation "Returns an SQL string appropriate for DATABASE + and corresponding to the lisp expression + EXPR. DATABASE-OUTPUT-SQL is called by OUTPUT-SQL when no more + specific method exists for EXPR.")) + +(defgeneric output-sql-hash-key (expr database) + (:documentation "Returns a list (or other object suitable for +use as the key of an EQUAL hash table) which uniquely identifies +the arguments EXPR and DATABASE.")) + +(defgeneric collect-table-refs (sql) + ) + +(defgeneric database-constraint-statement (constraints database) + ) + +(defgeneric database-translate-constraint (constraint database) + (:documentation "Given a column constraint returns its +database-specific name. For example, auto-increment constraints can +have different names in different database engines.")) + +(defgeneric filter-select-list ( view-class clsql-sys::select-list database) + (:documentation + "Gives fine grained control over sql to be executed and mapped to slots + called with a dummy instance (so that class precedence can be used)") + ) + +(defgeneric view-classes-and-storable-slots (view-class &key to-database-p) + (:documentation "A method that collects all the classes and storable slots + that need to be read from or written to the database. + to-database-p should be T if we are writing this object to the database + and nil when we are reading this object from the database")) diff --git a/sql/initialize.lisp b/sql/initialize.lisp new file mode 100644 index 0000000..cc827bb --- /dev/null +++ b/sql/initialize.lisp @@ -0,0 +1,61 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: initialize.lisp +;;;; Purpose: Initializion routines for db backend +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: May 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; and Copyright (c) 1999-2001 by Pierre R. Mai +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defvar *loaded-database-types* nil + "Contains a list of database types which have been defined/loaded.") + +(defmethod database-type-load-foreign (x) + (error "No generic function defined for database-type-load-foreign with parameters of ~S" x)) + +(defmethod database-type-load-foreign :after (database-type) + (when (database-type-library-loaded database-type) + (pushnew database-type *loaded-database-types*))) + +(defun reload-database-types () + "Reloads any foreign code for the loaded database types after a dump." + (mapc #'database-type-load-foreign *loaded-database-types*)) + +(defvar *default-database-type* nil + "Designates the default database type which is initialised by + the function INITIALISE-DATABASE-TYPE.") + +(defvar *initialized-database-types* nil + "A list of database types which have currently been initialised +by calling INITIALIZE-DATABASE-TYPE.") + +(defun initialize-database-type (&key (database-type *default-database-type*)) + "Initializes the supplied DATABASE-TYPE, if it is not already +initialized, as indicated by *INITIALIZED-DATABASE-TYPES* and +returns DATABASE-TYPE. *DEFAULT-DATABASE-TYPE* is set to +DATABASE-TYPE and, if DATABASE-TYPE has not been initialised, it +is added to *INITIALIZED-DATABASE-TYPES*. " + (when (member database-type *initialized-database-types*) + (return-from initialize-database-type database-type)) + + (let ((system (intern (concatenate 'string + (symbol-name '#:clsql-) + (symbol-name database-type))))) + (when (not (find-package system)) + (asdf:operate 'asdf:load-op system))) + + (when (database-initialize-database-type database-type) + (push database-type *initialized-database-types*) + (setf *default-database-type* database-type) + database-type)) + diff --git a/sql/kmr-mop.lisp b/sql/kmr-mop.lisp new file mode 100644 index 0000000..f355282 --- /dev/null +++ b/sql/kmr-mop.lisp @@ -0,0 +1,101 @@ +;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: kmr-mop.lisp +;;;; Purpose: MOP support for multiple-implementions +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Apr 2003 +;;;; +;;;; This file imports MOP symbols into the CLSQL-MOP package and then +;;;; re-exports into CLSQL-SYS them to hide differences in +;;;; MOP implementations. +;;;; +;;;; This file was extracted from the KMRCL utilities +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +#+lispworks +(defun intern-eql-specializer (slot) + `(eql ,slot)) + +(defmacro process-class-option (metaclass slot-name &optional required) + #+lispworks + `(defmethod clos:process-a-class-option ((class ,metaclass) + (name (eql ,slot-name)) + value) + (when (and ,required (null value)) + (error "metaclass ~A class slot ~A must have a value" (quote ,metaclass) name)) + (list name `',value)) + #-lispworks + (declare (ignore metaclass slot-name required)) + ) + +(defmacro process-slot-option (metaclass slot-name) + #+lispworks + `(defmethod clos:process-a-slot-option ((class ,metaclass) + (option (eql ,slot-name)) + value + already-processed-options + slot) + (list* option `',value already-processed-options)) + #-lispworks + (declare (ignore metaclass slot-name)) + ) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defclass %slot-order-test-class () + ((a) + (b))) + (finalize-inheritance (find-class '%slot-order-test-class)) + (let ((slots (class-slots (find-class '%slot-order-test-class)))) + (ecase (slot-definition-name (first slots)) + (a) + (b (pushnew :mop-slot-order-reversed cl:*features*))))) + +(defun ordered-class-slots (class) + #+mop-slot-order-reversed (reverse (class-slots class)) + #-mop-slot-order-reversed (class-slots class)) + +(defun ordered-class-direct-slots (class) + "Gets an ordered list of direct class slots" + ;; NB: this used to return effective-slot-definitions in direct + ;; opposition to the function name. Not sure why + (setf class (to-class class)) + #+mop-slot-order-reversed (reverse (class-direct-slots class)) + #-mop-slot-order-reversed (class-direct-slots class)) + +(defun find-slot-if (class predicate &optional direct? recurse?) + "Looks up a direct-slot-definition by name" + (setf class (to-class class)) + (labels ((find-it (class) + (let* ((slots (if direct? + (ordered-class-direct-slots class) + (ordered-class-slots class))) + (it (find-if predicate slots))) + (or it + (when recurse? + (loop for sup in (class-direct-superclasses class) + for rtn = (find-it sup) + until rtn + finally (return rtn))))))) + (find-it class))) + +(defun find-slot-by-name (class slot-name &optional direct? recurse?) + "Looks up a direct-slot-definition by name" + (setf class (to-class class) + slot-name (to-slot-name slot-name)) + (find-slot-if class (lambda (slot-def) (eql (to-slot-name slot-def) slot-name)) + direct? recurse?)) + +;; Lispworks has symbol for slot rather than the slot instance +(defun %svuc-slot-name (slot) + #+lispworks slot + #-lispworks (slot-definition-name slot)) + +(defun %svuc-slot-object (slot class) + (declare (ignorable class)) + #+lispworks (clos:find-slot-definition slot class) + #-lispworks slot) + diff --git a/sql/loop-extension.lisp b/sql/loop-extension.lisp new file mode 100644 index 0000000..32a356c --- /dev/null +++ b/sql/loop-extension.lisp @@ -0,0 +1,247 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: loop-extension.lisp +;;;; Purpose: Extensions to the Loop macro for CLSQL +;;;; +;;;; Copyright (c) 2001-2006 Kevin Rosenberg and (c) 1999-2001 Pierre R. Mai +;;;; +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +#+(or allegro sbcl) +(eval-when (:compile-toplevel :load-toplevel :execute) + (defpackage #:ansi-loop + (:import-from #+sbcl #:sb-loop #+allegro #:excl + #:*loop-epilogue* + #:*loop-ansi-universe* + #:add-loop-path))) + +#+(or allegro sbcl) +(defun ansi-loop::loop-gentemp (&optional (pref 'loopva-)) + (gensym (string pref))) + +#+clisp +(eval-when (:compile-toplevel :load-toplevel :execute) + (when (find-package "ANSI-LOOP") (push :clisp-aloop cl:*features*))) + +#+(or allegro clisp-aloop cmu openmcl sbcl scl) +(defun loop-record-iteration-path (variable data-type prep-phrases) + (let ((in-phrase nil) + (from-phrase nil)) + (loop for (prep . rest) in prep-phrases + do + (case prep + ((:in :of) + (when in-phrase + (error 'clsql:sql-user-error + :message + (format nil + "Duplicate OF or IN iteration path: ~S." + (cons prep rest)))) + (setq in-phrase rest)) + ((:from) + (when from-phrase + (error 'clsql:sql-user-error + :message + (format nil + "Duplicate FROM iteration path: ~S." + (cons prep rest)))) + (setq from-phrase rest)) + (t + (error 'clsql:sql-user-error + :message + (format nil"Unknown preposition: ~S." prep))))) + (unless in-phrase + (error 'clsql:sql-user-error + :message "Missing OF or IN iteration path.")) + (unless from-phrase + (setq from-phrase '(*default-database*))) + + (unless (consp variable) + (setq variable (list variable))) + + (cond + ;; object query + ((and (consp (first in-phrase)) + (string-equal "sql-query" (symbol-name (caar in-phrase))) + (consp (second (first in-phrase))) + (eq 'quote (first (second (first in-phrase)))) + (symbolp (second (second (first in-phrase))))) + + (let ((result-var (ansi-loop::loop-gentemp + 'loop-record-result-)) + (step-var (ansi-loop::loop-gentemp 'loop-record-step-))) + `(((,variable nil ,@(and data-type (list data-type))) + (,result-var (query ,(first in-phrase))) + (,step-var nil)) + () + () + () + (if (null ,result-var) + t + (progn + (setq ,step-var (first ,result-var)) + (setq ,result-var (rest ,result-var)) + nil)) + (,variable ,step-var) + (null ,result-var) + () + (if (null ,result-var) + t + (progn + (setq ,step-var (first ,result-var)) + (setq ,result-var (rest ,result-var)) + nil)) + (,variable ,step-var)))) + + ((consp variable) + (let ((query-var (ansi-loop::loop-gentemp 'loop-record-)) + (db-var (ansi-loop::loop-gentemp 'loop-record-database-)) + (result-set-var (ansi-loop::loop-gentemp + 'loop-record-result-set-)) + (step-var (ansi-loop::loop-gentemp 'loop-record-step-))) + (push `(when ,result-set-var + (database-dump-result-set ,result-set-var ,db-var)) + ansi-loop::*loop-epilogue*) + `(((,variable nil ,@(and data-type (list data-type))) + (,query-var ,(first in-phrase)) + (,db-var ,(first from-phrase)) + (,result-set-var t) + (,step-var nil)) + ((multiple-value-bind (%rs %cols) + (database-query-result-set ,query-var ,db-var :result-types :auto) + (setq ,result-set-var %rs ,step-var (make-list %cols)))) + () + () + (not (database-store-next-row ,result-set-var ,db-var ,step-var)) + (,variable ,step-var) + (not ,result-set-var) + () + (not (database-store-next-row ,result-set-var ,db-var ,step-var)) + (,variable ,step-var))))))) + +#+(or allegro clisp-aloop cmu openmcl sbcl scl) +(ansi-loop::add-loop-path '(record records tuple tuples) + 'loop-record-iteration-path + ansi-loop::*loop-ansi-universe* + :preposition-groups '((:of :in) (:from)) + :inclusive-permitted nil) + + +#+lispworks +(cl-user::define-loop-method (loop::record loop::records loop::tuple loop::tuples) + clsql-loop-method + (loop::in loop::of loop::from)) + +#+lispworks +(defun clsql-loop-method (method-name iter-var iter-var-data-type + prep-phrases inclusive? allowed-preps + method-specific-data) + (declare (ignore method-name iter-var-data-type inclusive? allowed-preps method-specific-data)) + (let ((in-phrase nil) + (from-phrase nil)) + (loop for (prep . rest) in prep-phrases + do + (cond + ((or (eq prep 'loop::in) (eq prep 'loop::of)) + (when in-phrase + (error 'clsql:sql-user-error + :message + (format nil "Duplicate OF or IN iteration path: ~S." + (cons prep rest)))) + (setq in-phrase rest)) + ((eq prep 'loop::from) + (when from-phrase + (error 'clsql:sql-user-error + :message + (format nil "Duplicate FROM iteration path: ~S." + (cons prep rest)))) + (setq from-phrase rest)) + (t + (error 'clsql:sql-user-error + :message (format nil "Unknown preposition: ~S." prep))))) + (unless in-phrase + (error 'clsql:sql-user-error + :message "Missing OF or IN iteration path.")) + (unless from-phrase + (setq from-phrase '(clsql:*default-database*))) + + (unless (consp iter-var) + (setq iter-var (list iter-var))) + + (cond + ;; object query + ((and (consp in-phrase) + (string-equal "sql-query" (symbol-name (car in-phrase))) + (consp (second in-phrase)) + (eq 'quote (first (second in-phrase))) + (symbolp (second (second in-phrase)))) + + (let ((result-var (gensym "LOOP-RECORD-RESULT-")) + (step-var (gensym "LOOP-RECORD-STEP-"))) + (values + t + nil + `(,@(mapcar (lambda (v) `(,v nil)) iter-var) + (,result-var (clsql:query ,in-phrase)) + (,step-var nil)) + () + () + () + `((if (null ,result-var) + t + (progn + (setq ,step-var (first ,result-var)) + (setq ,result-var (rest ,result-var)) + nil))) + `(,iter-var ,step-var) + `((if (null ,result-var) + t + (progn + (setq ,step-var (first ,result-var)) + (setq ,result-var (rest ,result-var)) + nil))) + `(,iter-var ,step-var) + () + () + ))) + + ((consp iter-var) + (let ((query-var (gensym "LOOP-RECORD-")) + (db-var (gensym "LOOP-RECORD-DATABASE-")) + (result-set-var (gensym "LOOP-RECORD-RESULT-SET-")) + (step-var (gensym "LOOP-RECORD-STEP-"))) + (values + t + nil + `(,@(mapcar (lambda (v) `(,v nil)) iter-var) + (,query-var ,in-phrase) + (,db-var ,(first from-phrase)) + (,result-set-var nil) + (,step-var nil)) + `((multiple-value-bind (%rs %cols) + (clsql-sys:database-query-result-set ,query-var ,db-var :result-types :auto) + (setq ,result-set-var %rs ,step-var (make-list %cols)))) + () + () + `((unless (clsql-sys:database-store-next-row ,result-set-var ,db-var ,step-var) + (when ,result-set-var + (clsql-sys:database-dump-result-set ,result-set-var ,db-var)) + t)) + `(,iter-var ,step-var) + `((unless (clsql-sys:database-store-next-row ,result-set-var ,db-var ,step-var) + (when ,result-set-var + (clsql-sys:database-dump-result-set ,result-set-var ,db-var)) + t)) + `(,iter-var ,step-var) + () + ())))))) + + +#+clisp-aloop +(eval-when (:compile-toplevel :load-toplevel :execute) + (setq cl:*features* (delete :clisp-aloop cl:*features*))) + diff --git a/sql/metaclasses.lisp b/sql/metaclasses.lisp new file mode 100644 index 0000000..9aa7dd9 --- /dev/null +++ b/sql/metaclasses.lisp @@ -0,0 +1,641 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; CLSQL metaclass for standard-db-objects created in the OODDL. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (when (>= (length (generic-function-lambda-list + (ensure-generic-function + 'compute-effective-slot-definition))) + 3) + (pushnew :kmr-normal-cesd cl:*features*)) + + (when (>= (length (generic-function-lambda-list + (ensure-generic-function + 'direct-slot-definition-class))) + 3) + (pushnew :kmr-normal-dsdc cl:*features*)) + + (when (>= (length (generic-function-lambda-list + (ensure-generic-function + 'effective-slot-definition-class))) + 3) + (pushnew :kmr-normal-esdc cl:*features*))) + + +;; ------------------------------------------------------------ +;; metaclass: view-class + +(defclass standard-db-class (standard-class) + ((view-table + :accessor view-table + :initarg :view-table) + (definition + :accessor object-definition + :initarg :definition + :initform nil) + (key-slots + :accessor key-slots + :initform nil) + (normalizedp + :accessor normalizedp + :initform nil) + (class-qualifier + :accessor view-class-qualifier + :initarg :qualifier + :initform nil)) + (:documentation "Metaclass for all CLSQL View Classes.")) + +;;; Lispworks 4.2 and before requires special processing of extra slot and class options + +(defvar +extra-slot-options+ '(:column :db-kind :db-type :db-reader :void-value :db-constraints + :db-writer :db-info)) +(defvar +extra-class-options+ '(:base-table)) + +#+lispworks +(dolist (slot-option +extra-slot-options+) + (eval `(process-slot-option standard-db-class ,slot-option))) + +#+lispworks +(dolist (class-option +extra-class-options+) + (eval `(process-class-option standard-db-class ,class-option))) + +(defmethod validate-superclass ((class standard-db-class) + (superclass standard-class)) + t) + +(defun table-name-from-arg (arg) + (cond ((symbolp arg) + (intern (sql-escape arg))) + ((typep arg 'sql-ident) + (if (symbolp (slot-value arg 'name)) + (intern (sql-escape (slot-value arg 'name))) + (sql-escape (slot-value arg 'name)))) + ((stringp arg) + (sql-escape arg)))) + +(defun remove-keyword-arg (arglist akey) + (let ((mylist arglist) + (newlist ())) + (labels ((pop-arg (alist) + (let ((arg (pop alist)) + (val (pop alist))) + (unless (equal arg akey) + (setf newlist (append (list arg val) newlist))) + (when alist (pop-arg alist))))) + (pop-arg mylist)) + newlist)) + +(defun set-view-table-slot (class base-table) + (setf (view-table class) + (table-name-from-arg (or (and base-table + (if (listp base-table) + (car base-table) + base-table)) + (class-name class))))) + +(defmethod initialize-instance :around ((class standard-db-class) + &rest all-keys + &key direct-superclasses base-table + qualifier normalizedp + &allow-other-keys) + (let ((root-class (find-class 'standard-db-object nil)) + (vmc 'standard-db-class)) + (setf (view-class-qualifier class) + (car qualifier)) + (if root-class + (if (some #'(lambda (super) (typep super vmc)) + direct-superclasses) + (call-next-method) + (apply #'call-next-method + class + :direct-superclasses (append (list root-class) + direct-superclasses) + (remove-keyword-arg all-keys :direct-superclasses))) + (call-next-method)) + (set-view-table-slot class base-table) + (setf (normalizedp class) (car normalizedp)) + (register-metaclass class (nth (1+ (position :direct-slots all-keys)) + all-keys)))) + +(defmethod reinitialize-instance :around ((class standard-db-class) + &rest all-keys + &key base-table normalizedp + direct-superclasses qualifier + &allow-other-keys) + (let ((root-class (find-class 'standard-db-object nil)) + (vmc 'standard-db-class)) + (set-view-table-slot class base-table) + (setf (normalizedp class) (car normalizedp)) + (setf (view-class-qualifier class) + (car qualifier)) + (if (and root-class (not (equal class root-class))) + (if (some #'(lambda (super) (typep super vmc)) + direct-superclasses) + (call-next-method) + (apply #'call-next-method + class + :direct-superclasses (append (list root-class) + direct-superclasses) + (remove-keyword-arg all-keys :direct-superclasses))) + (call-next-method))) + (register-metaclass class (nth (1+ (position :direct-slots all-keys)) + all-keys)) + class) + + +(defun get-keywords (keys list) + (flet ((extract (key) + (let ((pos (position key list))) + (when pos + (nth (1+ pos) list))))) + (mapcar #'extract keys))) + +(defun describe-db-layout (class) + (flet ((not-db-col (col) + (not (member (nth 2 col) '(nil :base :key)))) + (frob-slot (slot) + (let ((type (slot-definition-type slot))) + (if (eq type t) + (setq type nil)) + (list (slot-value slot 'name) + type + (slot-value slot 'db-kind) + (and (slot-boundp slot 'column) + (slot-value slot 'column)))))) + (let ((all-slots (mapcar #'frob-slot (ordered-class-slots class)))) + (setq all-slots (remove-if #'not-db-col all-slots)) + (setq all-slots (stable-sort all-slots #'string< :key #'car)) + ;;(mapcar #'dink-type all-slots) + all-slots))) + +(defun register-metaclass (class slots) + (labels ((not-db-col (col) + (not (member (nth 2 col) '(nil :base :key)))) + (frob-slot (slot) + (get-keywords '(:name :type :db-kind :column) slot))) + (let ((all-slots (mapcar #'frob-slot slots))) + (setq all-slots (remove-if #'not-db-col all-slots)) + (setq all-slots (stable-sort all-slots #'string< :key #'car)) + (setf (object-definition class) all-slots)) + #-(or sbcl allegro) + (setf (key-slots class) (remove-if-not (lambda (slot) + (eql (slot-value slot 'db-kind) + :key)) + (slots-for-possibly-normalized-class class))))) + +#+(or sbcl allegro) +(defmethod finalize-inheritance :after ((class standard-db-class)) + (setf (key-slots class) (remove-if-not (lambda (slot) + (eql (slot-value slot 'db-kind) + :key)) + (slots-for-possibly-normalized-class class)))) + +;; return the deepest view-class ancestor for a given view class + +(defun base-db-class (classname) + (let* ((class (find-class classname)) + (db-class (find-class 'standard-db-object))) + (loop + (let ((cds (class-direct-superclasses class))) + (cond ((null cds) + (error "not a db class")) + ((member db-class cds) + (return (class-name class)))) + (setq class (car cds)))))) + +(defun db-ancestors (classname) + (let ((class (find-class classname)) + (db-class (find-class 'standard-db-object))) + (labels ((ancestors (class) + (let ((scs (class-direct-superclasses class))) + (if (member db-class scs) + (list class) + (append (list class) (mapcar #'ancestors scs)))))) + (ancestors class)))) + +(defclass view-class-slot-definition-mixin () + ((column + :accessor view-class-slot-column + :initarg :column + :documentation + "The name of the SQL column this slot is stored in. Defaults to +the slot name.") + (db-kind + :accessor view-class-slot-db-kind + :initarg :db-kind + :initform :base + ;; openmcl 0.14.2 stores the value as list in the DSD + ;; :type (or list keyword) + #-openmcl :type #-openmcl keyword + :documentation + "The kind of DB mapping which is performed for this slot. :base +indicates the slot maps to an ordinary column of the DB view. :key +indicates that this slot corresponds to part of the unique keys for +this view, :join indicates ... and :virtual indicates that this slot +is an ordinary CLOS slot. Defaults to :base.") + (db-reader + :accessor view-class-slot-db-reader + :initarg :db-reader + :initform nil + :documentation + "If a string, then when reading values from the DB, the string +will be used for a format string, with the only value being the value +from the database. The resulting string will be used as the slot +value. If a function then it will take one argument, the value from +the database, and return the value that should be put into the slot.") + (db-writer + :accessor view-class-slot-db-writer + :initarg :db-writer + :initform nil + :documentation + "If a string, then when reading values from the slot for the DB, +the string will be used for a format string, with the only value being +the value of the slot. The resulting string will be used as the +column value in the DB. If a function then it will take one argument, +the value of the slot, and return the value that should be put into +the database.") + (db-type + :accessor view-class-slot-db-type + :initarg :db-type + :initform nil + :documentation + "A string which will be used as the type specifier for this slots +column definition in the database.") + (db-constraints + :accessor view-class-slot-db-constraints + :initarg :db-constraints + :initform nil + :documentation + "A keyword symbol representing a single SQL column constraint or list of such symbols.") + (void-value + :accessor view-class-slot-void-value + :initarg :void-value + :initform nil + :documentation + "Value to store if the SQL value is NULL. Default is NIL.") + (db-info + :accessor view-class-slot-db-info + :initarg :db-info + :documentation "Description of the join.") + (specified-type + :accessor specified-type + :initarg specified-type + :initform nil + :documentation "Internal slot storing the :type specified by user.") + (autoincrement-sequence + :accessor view-class-slot-autoincrement-sequence + :initarg :autoincrement-sequence + :initform nil + :documentation "A string naming the (possibly automatically generated) sequence +for a slot with an :auto-increment constraint."))) + +(defparameter *db-info-lambda-list* + '(&key join-class + home-key + foreign-key + (key-join nil) + (target-slot nil) + (retrieval :immmediate) + (set nil))) + +(defun parse-db-info (db-info-list) + (destructuring-bind + (&key join-class home-key key-join foreign-key (delete-rule nil) + (target-slot nil) (retrieval :deferred) (set t)) + db-info-list + (let ((ih (make-hash-table :size 6))) + (if join-class + (setf (gethash :join-class ih) join-class) + (error "Must specify :join-class in :db-info")) + (if home-key + (setf (gethash :home-key ih) home-key) + (error "Must specify :home-key in :db-info")) + (when delete-rule + (setf (gethash :delete-rule ih) delete-rule)) + (if foreign-key + (setf (gethash :foreign-key ih) foreign-key) + (error "Must specify :foreign-key in :db-info")) + (when key-join + (setf (gethash :key-join ih) t)) + (when target-slot + (setf (gethash :target-slot ih) target-slot)) + (when set + (setf (gethash :set ih) set)) + (when retrieval + (progn + (setf (gethash :retrieval ih) retrieval) + (if (eql retrieval :immediate) + (setf (gethash :set ih) nil)))) + ih))) + +(defclass view-class-direct-slot-definition (view-class-slot-definition-mixin + standard-direct-slot-definition) + ()) + +(defclass view-class-effective-slot-definition (view-class-slot-definition-mixin + standard-effective-slot-definition) + ()) + +(defmethod direct-slot-definition-class ((class standard-db-class) + #+kmr-normal-dsdc &rest + initargs) + (declare (ignore initargs)) + (find-class 'view-class-direct-slot-definition)) + +(defmethod effective-slot-definition-class ((class standard-db-class) + #+kmr-normal-esdc &rest + initargs) + (declare (ignore initargs)) + (find-class 'view-class-effective-slot-definition)) + +#+openmcl +(when (not (symbol-function 'compute-class-precedence-list)) + (eval + (defun compute-class-precedence-list (class) + (class-precedence-list class)))) + +#-mop-slot-order-reversed +(defmethod compute-slots ((class standard-db-class)) + "Need to sort order of class slots so they are the same across +implementations." + (let ((slots (call-next-method)) + desired-sequence + output-slots) + (dolist (c (compute-class-precedence-list class)) + (dolist (s (class-direct-slots c)) + (let ((name (slot-definition-name s))) + (unless (find name desired-sequence) + (push name desired-sequence))))) + (dolist (desired desired-sequence) + (let ((slot (find desired slots :key #'slot-definition-name))) + (assert slot) + (push slot output-slots))) + output-slots)) + +(defun compute-lisp-type-from-specified-type (specified-type db-constraints) + "Computes the Lisp type for a user-specified type." + (let ((type + (cond + ((consp specified-type) + (let* ((first (first specified-type)) + (name (etypecase first + (symbol (symbol-name first)) + (string first)))) + (cond + ((or (string-equal name "string") + (string-equal name "varchar") + (string-equal name "char")) + 'string) + (t + specified-type)))) + ((eq (ensure-keyword specified-type) :bigint) + 'integer) + ((eq (ensure-keyword specified-type) :char) + 'character) + ((eq (ensure-keyword specified-type) :varchar) + 'string) + (t + specified-type)))) + (if (and type (not (member :not-null (listify db-constraints)))) + `(or null ,type) + (or type t)))) + +;; Compute the slot definition for slots in a view-class. Figures out +;; what kind of database value (if any) is stored there, generates and +;; verifies the column name. + +(declaim (inline delistify)) +(defun delistify (list) + "Some MOPs, like openmcl 0.14.2, cons attribute values in a list." + (if (listp list) + (car list) + list)) + +(declaim (inline delistify-dsd)) +;; there is an :after method below too +(defmethod initialize-instance :around + ((obj view-class-direct-slot-definition) + &rest initargs &key db-constraints db-kind type &allow-other-keys) + (when (and (not db-kind) (member :primary-key (listify db-constraints))) + (warn "Slot ~S constrained to be :primary-key, but not marked as :db-kind :key" + (slot-definition-name obj))) + (apply #'call-next-method obj + 'specified-type type + :type (if (and (eql db-kind :virtual) (null type)) + t + (compute-lisp-type-from-specified-type + type db-constraints)) + initargs)) + +(defun compute-column-name (arg) + (database-identifier arg nil)) + +(defun %convert-db-info-to-hash (slot-def) + ;; I wonder if this slot option and the previous could be merged, + ;; so that :base and :key remain keyword options, but :db-kind + ;; :join becomes :db-kind (:join )? + (setf (slot-value slot-def 'db-info) + (when (slot-boundp slot-def 'db-info) + (let ((info (view-class-slot-db-info slot-def))) + (etypecase info + (hash-table info) + (atom info) + (list + (cond ((and (> (length info) 1) + (atom (car info))) + (parse-db-info info)) + ((and (= 1 (length info)) + (listp (car info))) + (parse-db-info (car info))) + (t info)))))))) + +(defmethod initialize-instance :after + ((obj view-class-direct-slot-definition) + &key &allow-other-keys) + (setf (view-class-slot-column obj) (compute-column-name obj) + (view-class-slot-autoincrement-sequence obj) + (dequote + (view-class-slot-autoincrement-sequence obj))) + (%convert-db-info-to-hash obj)) + +(defmethod compute-effective-slot-definition ((class standard-db-class) + #+kmr-normal-cesd slot-name + direct-slots) + #+kmr-normal-cesd (declare (ignore slot-name)) + + ;; KMR: store the user-specified type and then compute + ;; real Lisp type and store it + (let ((dsd (car direct-slots))) + (let ((esd (call-next-method))) + (typecase dsd + (view-class-slot-definition-mixin + (setf (slot-value esd 'column) (compute-column-name dsd)) + + (macrolet + ((safe-copy-value (name &optional default) + (let ((fn (intern (format nil "~A~A" 'view-class-slot- name )))) + `(setf (slot-value esd ',name) + (or (when (slot-boundp dsd ',name) + (delistify-dsd (,fn dsd))) + ,default))))) + (safe-copy-value autoincrement-sequence) + (safe-copy-value db-type) + (safe-copy-value void-value) + (safe-copy-value db-reader) + (safe-copy-value db-writer) + ;; :db-kind slot value defaults to :base (store slot value in + ;; database) + (safe-copy-value db-kind :base) + (safe-copy-value db-constraints) + (safe-copy-value db-info) + (%convert-db-info-to-hash esd)) + + (setf (specified-type esd) + (delistify-dsd (specified-type dsd))) + ;; In older SBCL's the type-check-function is computed at + ;; defclass expansion, which is too early for the CLSQL type + ;; conversion to take place. This gets rid of it. It's ugly + ;; but it's better than nothing -wcp10/4/10. + #+(and sbcl #.(cl:if (cl:and (cl:find-package :sb-pcl) + (cl:find-symbol "%TYPE-CHECK-FUNCTION" :sb-pcl)) + '(cl:and) '(cl:or))) + (setf (slot-value esd 'sb-pcl::%type-check-function) nil) + + ) + ;; all other slots + (t + (unless (typep esd 'view-class-effective-slot-definition) + (warn "Non view-class-direct-slot object with non-view-class-effective-slot-definition in compute-effective-slot-definition") + + (let ((type-predicate #+openmcl (slot-value esd 'ccl::type-predicate))) + #-openmcl (declare (ignore type-predicate)) + #-(or clisp sbcl) (change-class esd 'view-class-effective-slot-definition + #+allegro :name + #+allegro (slot-definition-name dsd)) + #+openmcl (setf (slot-value esd 'ccl::type-predicate) + type-predicate))) + + ;; has no column name if it is not a database column + (setf (slot-value esd 'column) nil) + (setf (slot-value esd 'db-info) nil) + (setf (slot-value esd 'db-kind) :virtual) + (setf (specified-type esd) (slot-definition-type dsd))) + ) + esd))) + +(defun slotdefs-for-slots-with-class (slots class) + (let ((result nil)) + (dolist (s slots) + (let ((c (slotdef-for-slot-with-class s class))) + (if c (setf result (cons c result))))) + result)) + +(defun slotdef-for-slot-with-class (slot class) + (typecase slot + (standard-slot-definition slot) + (symbol (find-slot-by-name class slot)))) + +#+ignore +(eval-when (:compile-toplevel :load-toplevel :execute) + #+kmr-normal-cesd + (setq cl:*features* (delete :kmr-normal-cesd cl:*features*)) + #+kmr-normal-dsdc + (setq cl:*features* (delete :kmr-normal-dsdc cl:*features*)) + #+kmr-normal-esdc + (setq cl:*features* (delete :kmr-normal-esdc cl:*features*)) + ) + +(defmethod database-identifier ( (name standard-db-class) + &optional database find-class-p) + "the majority of this function is in expressions.lisp + this is here to make loading be less painful (try-recompiles) in SBCL" + (declare (ignore find-class-p)) + (database-identifier (view-table name) database)) + +(defmethod database-identifier ((name view-class-slot-definition-mixin) + &optional database find-class-p) + (declare (ignore find-class-p)) + (database-identifier + (if (slot-boundp name 'column) + (delistify-dsd (view-class-slot-column name)) + (slot-definition-name name)) + database)) + +(defun find-standard-db-class (name &aux cls) + (and (setf cls (ignore-errors (find-class name))) + (typep cls 'standard-db-class) + cls)) + +(defun slots-for-possibly-normalized-class (class) + "Get the slots for this class, if normalized this is only the direct slots + otherwiese its all the slots" + (if (normalizedp class) + (ordered-class-direct-slots class) + (ordered-class-slots class))) + + +(defun key-slot-p (slot-def) + "takes a slot def and returns whether or not it is a key" + (eql :key (view-class-slot-db-kind slot-def))) + +(defun join-slot-p (slot-def) + "takes a slot def and returns whether or not it is a join slot" + (eql :join (view-class-slot-db-kind slot-def))) + +(defun join-slot-info-value (slot-def key) + "Get the join-slot db-info value associated with a key" + (when (join-slot-p slot-def) + (let ((dbi (view-class-slot-db-info slot-def))) + (when dbi (gethash key dbi))))) + +(defun join-slot-retrieval-method (slot-def) + "if this is a join slot return the retrieval param in the db-info" + (join-slot-info-value slot-def :retrieval)) + +(defun join-slot-class-name (slot-def) + "get the join class name for a given join slot" + (join-slot-info-value slot-def :join-class)) + +(defun join-slot-class (slot-def) + "Get the join class for a given join slot" + (let ((c (join-slot-class-name slot-def))) + (when c (find-class c)))) + +(defun key-or-base-slot-p (slot-def) + "takes a slot def and returns whether or not it is a key" + (member (view-class-slot-db-kind slot-def) '(:key :base))) + +(defun direct-normalized-slot-p (class slot-name) + "Is this a normalized class and if so is the slot one of our direct slots?" + (setf slot-name (to-slot-name slot-name)) + (and (typep class 'standard-db-class) + (normalizedp class) + (member slot-name (ordered-class-direct-slots class) + :key #'slot-definition-name))) + +(defun not-direct-normalized-slot-p (class slot-name) + "Is this a normalized class and if so is the slot not one of our direct slots?" + (setf slot-name (to-slot-name slot-name)) + (and (typep class 'standard-db-class) + (normalizedp class) + (not (member slot-name (ordered-class-direct-slots class) + :key #'slot-definition-name)))) + +(defun slot-has-default-p (slot) + "returns nil if the slot does not have a default constraint" + (let* ((constraints + (when (typep slot '(or view-class-direct-slot-definition + view-class-effective-slot-definition)) + (listify (view-class-slot-db-constraints slot))))) + (member :default constraints))) + diff --git a/sql/ooddl.lisp b/sql/ooddl.lisp new file mode 100644 index 0000000..5832283 --- /dev/null +++ b/sql/ooddl.lisp @@ -0,0 +1,248 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; The CLSQL Object Oriented Data Definitional Language (OODDL) +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + + +(in-package #:clsql-sys) + +(defclass standard-db-object () + ((view-database :initform nil :initarg :view-database :reader view-database + :db-kind :virtual)) + (:metaclass standard-db-class) + (:documentation "Superclass for all CLSQL View Classes.")) + +(defparameter *default-string-length* 255 + "The length of a string which does not have a user-specified length.") + +(defvar *db-auto-sync* nil + "A non-nil value means that creating View Class instances or + setting their slots automatically creates/updates the + corresponding records in the underlying database.") + +(defvar *db-deserializing* nil) +(defvar *db-initializing* nil) + +(defmethod slot-value-using-class ((class standard-db-class) instance slot-def) + "When a slot is unbound but should contain a join object or a value from a + normalized view-class, then retrieve and set those slots, so the value can + be returned" + (declare (optimize (speed 3))) + (unless *db-deserializing* + (let* ((slot-name (%svuc-slot-name slot-def)) + (slot-object (%svuc-slot-object slot-def class))) + (unless (slot-boundp instance slot-name) + (let ((*db-deserializing* t)) + (cond + ((join-slot-p slot-def) + (setf (slot-value instance slot-name) + (if (view-database instance) + (fault-join-slot class instance slot-object) + ;; TODO: you could in theory get a join object even if + ;; its joined-to object was not in the database + nil + ))) + ((not-direct-normalized-slot-p class slot-def) + (if (view-database instance) + (update-fault-join-normalized-slot class instance slot-def) + (setf (slot-value instance slot-name) nil)))))))) + (call-next-method)) + +(defmethod (setf slot-value-using-class) (new-value (class standard-db-class) + instance slot-def) + "Handle auto syncing values to the database if *db-auto-sync* is t" + (declare (ignore new-value)) + (let* ((slot-name (%svuc-slot-name slot-def)) + (slot-object (%svuc-slot-object slot-def class)) + (slot-kind (view-class-slot-db-kind slot-object))) + (prog1 + (call-next-method) + (when (and *db-auto-sync* + (not *db-initializing*) + (not *db-deserializing*) + (not (eql slot-kind :virtual))) + (update-record-from-slot instance slot-name))))) + +(defmethod initialize-instance ((object standard-db-object) + &rest all-keys &key &allow-other-keys) + (declare (ignore all-keys)) + (let ((*db-initializing* t)) + (call-next-method) + (when (and *db-auto-sync* + (not *db-deserializing*)) + (update-records-from-instance object)))) + +;; +;; Build the database tables required to store the given view class +;; + +(defun create-view-from-class (view-class-name + &key (database *default-database*) + (transactions t)) + "Creates a table as defined by the View Class VIEW-CLASS-NAME +in DATABASE which defaults to *DEFAULT-DATABASE*." + (let ((tclass (find-class view-class-name))) + (if tclass + (let ((*default-database* database) + (pclass (car (class-direct-superclasses tclass)))) + (when (and (normalizedp tclass) (not (table-exists-p pclass))) + (create-view-from-class (class-name pclass) + :database database :transactions transactions)) + (%install-class tclass database :transactions transactions)) + (error "Class ~s not found." view-class-name))) + (values)) + +(defmethod auto-increment-column-p (slotdef &optional (database clsql-sys:*default-database*)) + (declare (ignore database)) + (or (intersection + +auto-increment-names+ + (listify (view-class-slot-db-constraints slotdef))) + (slot-value slotdef 'autoincrement-sequence))) + +(defmethod %install-class ((self standard-db-class) database + &key (transactions t)) + (let ((schemadef '()) + (ordered-slots (slots-for-possibly-normalized-class self))) + (dolist (slotdef ordered-slots) + (let ((res (database-generate-column-definition self slotdef database))) + (when res + (push res schemadef)))) + (if (not schemadef) + (unless (normalizedp self) + (error "Class ~s has no :base slots" self)) + (progn + (database-add-autoincrement-sequence self database) + (create-table (sql-expression :table (database-identifier self database)) + (nreverse schemadef) + :database database + :transactions transactions + :constraints (database-pkey-constraint self database)) + (push self (database-view-classes database))))) + t) + +(defmethod database-pkey-constraint ((class standard-db-class) database) + ;; Keylist will always be a list of escaped-indentifier + (let ((keylist (mapcar #'(lambda (x) (escaped-database-identifier x database)) + (keyslots-for-class class))) + (table (escaped (combine-database-identifiers + (list class 'PK) + database)))) + (when keylist + (format nil "CONSTRAINT ~A PRIMARY KEY (~{~A~^,~})" table + keylist)))) + +(defmethod database-generate-column-definition (class slotdef database) + (declare (ignore class)) + (when (key-or-base-slot-p slotdef) + (let ((cdef + (list (sql-expression :attribute (database-identifier slotdef database)) + (specified-type slotdef)))) + (setf cdef (append cdef (list (view-class-slot-db-type slotdef)))) + (let ((const (view-class-slot-db-constraints slotdef))) + (when const + (setq cdef (append cdef (listify const))))) + cdef))) + + +;; +;; Drop the tables which store the given view class +;; + +(defun drop-view-from-class (view-class-name &key (database *default-database*) + (owner nil)) + "Removes a table defined by the View Class VIEW-CLASS-NAME from +DATABASE which defaults to *DEFAULT-DATABASE*." + (let ((tclass (find-class view-class-name))) + (if tclass + (let ((*default-database* database)) + (%uninstall-class tclass :owner owner)) + (error "Class ~s not found." view-class-name))) + (values)) + +(defun %uninstall-class (self &key + (database *default-database*) + (owner nil)) + (drop-table (sql-expression :table (database-identifier self database)) + :if-does-not-exist :ignore + :database database + :owner owner) + (database-remove-autoincrement-sequence self database) + (setf (database-view-classes database) + (remove self (database-view-classes database)))) + + +;; +;; List all known view classes +;; + +(defun list-classes (&key (test #'identity) + (root-class (find-class 'standard-db-object)) + (database *default-database*)) + "Returns a list of all the View Classes which are connected to +DATABASE, which defaults to *DEFAULT-DATABASE*, and which descend +from the class ROOT-CLASS and which satisfy the function TEST. By +default ROOT-CLASS is STANDARD-DB-OBJECT and TEST is IDENTITY." + (flet ((find-superclass (class) + (member root-class (class-precedence-list class)))) + (let ((view-classes (and database (database-view-classes database)))) + (when view-classes + (remove-if #'(lambda (c) (or (not (funcall test c)) + (not (find-superclass c)))) + view-classes))))) + +;; +;; Define a new view class +;; + +(defmacro def-view-class (class supers slots &rest cl-options) + "Creates a View Class called CLASS whose slots SLOTS can map +onto the attributes of a table in a database. If SUPERS is nil +then the superclass of CLASS will be STANDARD-DB-OBJECT, +otherwise SUPERS is a list of superclasses for CLASS which must +include STANDARD-DB-OBJECT or a descendent of this class. The +syntax of DEFCLASS is extended through the addition of a class +option :base-table which defines the database table onto which +the View Class maps and which defaults to CLASS. The DEFCLASS +syntax is also extended through additional slot +options. The :db-kind slot option specifies the kind of DB +mapping which is performed for this slot and defaults to :base +which indicates that the slot maps to an ordinary column of the +database table. A :db-kind value of :key indicates that this slot +is a special kind of :base slot which maps onto a column which is +one of the unique keys for the database table, the value :join +indicates this slot represents a join onto another View Class +which contains View Class objects, and the value :virtual +indicates a standard CLOS slot which does not map onto columns of +the database table. If a slot is specified with :db-kind :join, +the slot option :db-info contains a list which specifies the +nature of the join. For slots of :db-kind :base or :key, +the :type slot option has a special interpretation such that Lisp +types, such as string, integer and float are automatically +converted into appropriate SQL types for the column onto which +the slot maps. This behaviour may be over-ridden using +the :db-type slot option which is a string specifying the +vendor-specific database type for this slot's column definition +in the database. The :column slot option specifies the name of +the SQL column which the slot maps onto, if :db-kind is +not :virtual, and defaults to the slot name. The :void-value slot +option specifies the value to store if the SQL value is NULL and +defaults to NIL. The :db-constraints slot option is a string +representing an SQL table constraint expression or a list of such +strings." + `(progn + (defclass ,class ,supers ,slots + ,@(if (find :metaclass `,cl-options :key #'car) + `,cl-options + (cons '(:metaclass clsql-sys::standard-db-class) `,cl-options))) + (finalize-inheritance (find-class ',class)) + (find-class ',class))) + +(defun keyslots-for-class (class) + (slot-value class 'key-slots)) diff --git a/sql/oodml.lisp b/sql/oodml.lisp new file mode 100644 index 0000000..072bc4a --- /dev/null +++ b/sql/oodml.lisp @@ -0,0 +1,1353 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; The CLSQL Object Oriented Data Manipulation Language (OODML). +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defun find-normalized-key (obj) + "Find the first / primary key of a normalized object" + (find-slot-if obj #'key-slot-p T T)) + +(defun normalized-key-value (obj) + "Normalized classes share a single key for all their key slots" + (when (normalizedp (class-of obj)) + (easy-slot-value obj (find-normalized-key obj)))) + +(defun key-qualifier-for-instance (obj &key (database *default-database*) this-class) + "Generate a boolean sql-expression that identifies an object by its keys" + (let* ((obj-class (or this-class (class-of obj))) + (keys (keyslots-for-class obj-class)) + (normal-db-value (normalized-key-value obj))) + (when keys + (labels ((db-value (k) + (or normal-db-value + (db-value-from-slot + k + (easy-slot-value obj k) + database))) + (key-equal-exp (k) + (sql-operation '== (generate-attribute-reference obj-class k database) + (db-value k)))) + (clsql-ands (mapcar #'key-equal-exp keys)))))) + +(defun generate-attribute-reference (vclass slotdef &optional (database *default-database*)) + "Turns key class and slot-def into a sql-expression representing the + table and column it comes from + + used by things like make-select-list, update-slot-from-record" + (when (key-or-base-slot-p slotdef) + (sql-expression :attribute (database-identifier slotdef database) + :table (database-identifier vclass database)))) + +(defun get-join-slots (class &optional retrieval-method) + "Returns list of join slots for a class. + + if a retrieval method is specified only return slots of that type + if the retrieval method is T, nil or :all return all join slots" + (assert (member retrieval-method '(nil t :all :immediate :deferred))) + (setf class (to-class class)) + (let ((all? (member retrieval-method '(nil t :all)))) + (loop for slot in (ordered-class-slots class) + when (and (join-slot-p slot) + (or all? (eql (join-slot-retrieval-method slot) retrieval-method))) + collect slot))) + +(defun immediate-join-slots (class) + (get-join-slots class :immediate)) + +(defmethod choose-database-for-instance ((obj standard-db-object) &optional database) + "Determine which database connection to use for a standard-db-object. + Errs if none is available." + (or (find-if #'(lambda (db) + (and db (is-database-open db))) + (list (view-database obj) + database + *default-database*)) + (signal-no-database-error nil))) + + + +(defmethod update-slot-with-null ((object standard-db-object) slotdef) + "sets a slot to the void value of the slot-def (usually nil)" + (setf (easy-slot-value object slotdef) + (slot-value slotdef 'void-value))) + +(defmethod update-slot-from-db-value ((instance standard-db-object) slotdef value) + "This gets a value from the database and turns it itno a lisp value + based on the slot's slot-db-reader or baring that read-sql-value" + (declare (optimize (speed 3) #+cmu (extensions:inhibit-warnings 3))) + (let* ((slot-reader (view-class-slot-db-reader slotdef)) + (slot-type (specified-type slotdef))) + (cond + ((null value) (update-slot-with-null instance slotdef)) + ((null slot-reader) + (setf (easy-slot-value instance slotdef) + (read-sql-value value (delistify slot-type) + (choose-database-for-instance instance) + (database-underlying-type + (choose-database-for-instance instance))))) + (t (etypecase slot-reader + ((or symbol function) + (setf (easy-slot-value instance slotdef) + (apply slot-reader (list value)))) + (string + (setf (easy-slot-value instance slotdef) + (format nil slot-reader value)))))))) + +(defmethod key-value-from-db (slotdef value database) + "TODO: is this deprecated? there are no uses anywhere in clsql" + (declare (optimize (speed 3) #+cmu (extensions:inhibit-warnings 3))) + (let ((slot-reader (view-class-slot-db-reader slotdef)) + (slot-type (specified-type slotdef))) + (cond ((and value (null slot-reader)) + (read-sql-value value (delistify slot-type) database + (database-underlying-type database))) + ((null value) + nil) + ((typep slot-reader 'string) + (format nil slot-reader value)) + ((typep slot-reader '(or symbol function)) + (apply slot-reader (list value))) + (t + (error "Slot reader is of an unusual type."))))) + +(defun db-value-from-slot (slotdef val database) + (let ((dbwriter (view-class-slot-db-writer slotdef)) + (dbtype (specified-type slotdef))) + (typecase dbwriter + (string (format nil dbwriter val)) + ((and (or symbol function) (not null)) (apply dbwriter (list val))) + (t + (database-output-sql-as-type + (typecase dbtype + (cons (car dbtype)) + (t dbtype)) + val database (database-underlying-type database)))))) + +(defun check-slot-type (slotdef val) + (let* ((slot-type (specified-type slotdef)) + (basetype (if (listp slot-type) (car slot-type) slot-type))) + (when (and slot-type val) + (unless (typep val basetype) + (error 'sql-user-error + :message + (format nil "Invalid value ~A in slot ~A, not of type ~A." + val (slot-definition-name slotdef) slot-type)))))) + +(defmethod get-slot-values-from-view (obj slotdeflist values) + "Used to copy values from the database into the object + used by things like find-all and select" + (loop for slot in slotdeflist + for value in values + do (update-slot-from-db-value obj slot value)) + obj) + +(defclass class-and-slots () + ((view-class :accessor view-class :initarg :view-class :initform nil) + (slot-defs :accessor slot-defs :initarg :slot-defs :initform nil)) + (:documentation "A helper class to keep track of which slot-defs from a + table need to be updated, a normalized class might have many of these + because each of its parent classes might represent some other table and we + need to match which slots came from which parent class/table")) + +(defun make-class-and-slots (c &optional s) + "Create a new class-and-slots object" + (make-instance 'class-and-slots :view-class c :slot-defs (listify s) )) + +(defmethod view-table ((o class-and-slots)) + "get the view-table of the view-class of o" + (view-table (view-class o))) + +(defmethod view-table-exp ((o class-and-slots)) + (sql-expression :table (view-table o))) + +(defmethod view-table-exp ((o standard-db-class)) + (sql-expression :table (view-table o))) + +(defmethod attribute-references ((o class-and-slots)) + "build sql-ident-attributes for a given class-and-slots" + (loop + with class = (view-class o) + for sd in (slot-defs o) + collect (generate-attribute-reference class sd))) + +(defmethod attribute-value-pairs ((def class-and-slots) (o standard-db-object) + database) + "for a given class-and-slots and object, create the sql-expression & value pairs + that need to be sent to the database" + (loop for s in (slot-defs def) + for n = (to-slot-name s) + when (slot-boundp o n) + collect (make-attribute-value-pair s (slot-value o n) database))) + +(defmethod view-classes-and-slots-by-name ((obj standard-db-object) slots-to-match) + "If it's normalized, find the class that actually contains + the slot that's tied to the db, + + otherwise just search the current class + " + (let* ((view-class (class-of obj)) + (normalizedp (normalizedp view-class)) + rtns) + (labels ((get-c&s-obj (class) + (or (find class rtns :key #'view-class) + (first (push (make-class-and-slots class) rtns)))) + (associate-slot-with-class (class slot) + "Find the best class to associate with the slot. If it is + normalized then it needs to be a direct slot otherwise it just + needs to be on the class." + (let ((sd (find-slot-by-name class slot normalizedp nil))) + (if sd + ;;we found it directly or it's (not normalized) + (pushnew sd (slot-defs (get-c&s-obj class))) + (when normalizedp + (loop for parent in (class-direct-superclasses class) + until (associate-slot-with-class parent slot)))) + sd))) + (loop + for in-slot in (listify slots-to-match) + do (associate-slot-with-class view-class in-slot))) + rtns)) + +(defun update-auto-increments-keys (class obj database) + " handle pulling any autoincrement values into the object + Also handles normalized key chaining" + (let ((pk-slots (keyslots-for-class class)) + (table (view-table class)) + new-pk-value) + (labels ((do-update (slot &aux (val (easy-slot-value obj slot))) + (if val + (setf new-pk-value val) + (update-slot-from-db-value + obj slot + (or new-pk-value + (setf new-pk-value + (database-last-auto-increment-id + database table slot)))))) + ;; NB: This interacts very strangely with autoincrement keys + ;; (see changelog 2014-01-30) + (chain-primary-keys (in-class) + "This seems kindof wrong, but this is mostly how it was working, so + its here to keep the normalized code path working" + (when (typep in-class 'standard-db-class) + (loop for slot in (ordered-class-slots in-class) + when (key-slot-p slot) + do (do-update slot))))) + (loop for slot in pk-slots do (do-update slot)) + (let ((direct-class (to-class obj))) + (when (and new-pk-value (normalizedp direct-class)) + (chain-primary-keys direct-class))) + new-pk-value))) + +(defmethod %update-instance-helper + (class-and-slots obj database + &aux (avps (attribute-value-pairs class-and-slots obj database))) + "A function to help us update a given table (based on class-and-slots) + with values from an object" + ;; we dont actually need to update anything on this particular + ;; class / parent class + (unless avps (return-from %update-instance-helper)) + + (let* ((view-class (view-class class-and-slots)) + (table (view-table view-class)) + (table-sql (sql-expression :table table))) + + ;; view database is the flag we use to tell it was pulled from a database + ;; and thus probably needs an update instead of an insert + (cond ((view-database obj) + (let ((where (key-qualifier-for-instance + obj :database database :this-class view-class))) + (unless where + (error "update-record-from-*: could not generate a where clause for ~a using ~A" + obj view-class)) + (update-records table-sql + :av-pairs avps + :where where + :database database))) + (T ;; was not pulled from the db so insert it + ;; avps MUST contain any primary key slots set + ;; by previous inserts of the same object into different + ;; tables (ie: normalized stuff) + (insert-records :into table-sql + :av-pairs avps + :database database) + ;; also handles normalized-class key chaining + (update-auto-increments-keys view-class obj database) + ;; we dont set view database here, because there could be + ;; N of these for each call to update-record-from-* because + ;; of normalized classes + )) + (update-slot-default-values obj class-and-slots))) + +(defmethod update-record-from-slots ((obj standard-db-object) slots + &key (database *default-database*)) + "For a given list of slots, update all records associated with those slots + and classes. + + Generally this will update the single record associated with this object, + but for normalized classes might update as many records as there are + inheritances " + (setf slots (listify slots)) + (let* ((classes-and-slots (view-classes-and-slots-by-name obj slots)) + (database (choose-database-for-instance obj database))) + (loop for class-and-slots in classes-and-slots + do (%update-instance-helper class-and-slots obj database)) + (setf (slot-value obj 'view-database) database)) + (values)) + +(defmethod update-record-from-slot + ((obj standard-db-object) slot &key (database *default-database*)) + "just call update-records-from-slots which now handles this. + + This function is only here to maintain backwards compatibility in + the public api" + (update-record-from-slots obj slot :database database)) + +(defmethod view-classes-and-storable-slots (class &key to-database-p) + "Get a list of all the tables we need to update and the slots on them + + for non normalized classes we return the class and all its storable slots + + for normalized classes we return a list of direct slots and the class they + came from for each normalized view class + + to-database-p is provided so that we can read / write different data + to the database in different circumstances + (specifically clsql-helper:dirty-db-slots-mixin which only updates slots + that have changed ) + " + (setf class (to-class class)) + (let* (rtns) + (labels ((storable-slots (class) + (loop for sd in (slots-for-possibly-normalized-class class) + when (and (key-or-base-slot-p sd) + ;; we dont want to insert/update auto-increments + ;; but we do read them + (not (and to-database-p (auto-increment-column-p sd)))) + collect sd)) + (get-classes-and-slots (class &aux (normalizedp (normalizedp class))) + (let ((slots (storable-slots class))) + (when slots + (push (make-class-and-slots class slots) rtns))) + (when normalizedp + (loop for new-class in (class-direct-superclasses class) + do (when (typep new-class 'standard-db-class) + (get-classes-and-slots new-class)))))) + (get-classes-and-slots class)) + rtns)) + +(defmethod primary-key-slot-values ((obj standard-db-object) + &key class slots ) + "Returns the values of all key-slots for a given class" + (defaulting class (class-of obj) + slots (keyslots-for-class class)) + (loop for slot in slots + collect (easy-slot-value obj slot))) + +(defmethod update-slot-default-values ((obj standard-db-object) + classes-and-slots) + "Makes sure that if a class has unfilled slots that claim to have a default, + that we retrieve those defaults from the database + + TODO: use update-slots-from-record (doesnt exist) instead to batch this!" + (loop for class-and-slots in (listify classes-and-slots) + do (loop for slot in (slot-defs class-and-slots) + do (when (and (slot-has-default-p slot) + (not (easy-slot-value obj slot))) + (update-slot-from-record obj (to-slot-name slot)))))) + +(defmethod update-records-from-instance ((obj standard-db-object) + &key (database *default-database*)) + "Updates the records in the database associated with this object if + view-database slot on the object is nil then the object is assumed to be + new and is inserted" + (let ((database (choose-database-for-instance obj database)) + (classes-and-slots (view-classes-and-storable-slots obj :to-database-p t))) + (loop for class-and-slots in classes-and-slots + do (%update-instance-helper class-and-slots obj database)) + (setf (slot-value obj 'view-database) database) + (primary-key-slot-values obj))) + +(defmethod delete-instance-records ((instance standard-db-object) &key database) + "Removes the records associated with a given instance + (as determined by key-qualifier-for-instance) + + TODO: Doesnt handle normalized classes at all afaict" + (let ((database (choose-database-for-instance instance database)) + (vt (sql-expression :table (view-table (class-of instance))))) + (if database + (let ((qualifier (key-qualifier-for-instance instance :database database))) + (delete-records :from vt :where qualifier :database database) + (setf (record-caches database) nil) + (setf (slot-value instance 'view-database) nil) + (values)) + (signal-no-database-error database)))) + +(defmethod update-instance-from-records ((instance standard-db-object) + &key (database *default-database*)) + "Updates a database object with the current values stored in the database + + TODO: Should this update immediate join slots similar to build-objects? + Can we just call build-objects?, update-objects-joins? + " + + (let* ((classes-and-slots (view-classes-and-storable-slots + instance :to-database-p nil)) + (vd (choose-database-for-instance instance database))) + (labels ((do-update (class-and-slots) + (let* ((select-list (make-select-list class-and-slots + :do-joins-p nil + :database database)) + (view-table (sql-table select-list)) + (view-qual (key-qualifier-for-instance + instance :database vd + :this-class (view-class select-list))) + (res (when view-qual + (first + (apply #'select + (append (full-select-list select-list) + (list :from view-table + :where view-qual + :result-types nil + :database vd))))))) + (when res + (setf (slot-value instance 'view-database) vd) + (get-slot-values-from-view instance (slot-list select-list) res)) + ))) + (loop for class-and-slots in classes-and-slots + do (do-update class-and-slots))))) + + +(defmethod get-slot-value-from-record ((instance standard-db-object) + slot &key (database *default-database*)) + (let* ((class-and-slot + (first + (view-classes-and-slots-by-name instance slot))) + (view-class (view-class class-and-slot)) + (slot-def (first (slot-defs class-and-slot))) + (vd (choose-database-for-instance instance database)) + (att-ref (first (attribute-references class-and-slot))) + (res (first + (select att-ref + :from (view-table-exp class-and-slot) + :where (key-qualifier-for-instance + instance + :database vd + :this-class view-class) + :result-types nil + :flatp T)))) + (values res slot-def))) + +(defmethod update-slot-from-record ((instance standard-db-object) + slot &key (database *default-database*)) + "Pulls the value of a given slot form the database and stores that in the + appropriate slot on instance" + (multiple-value-bind (res slot-def) + (get-slot-value-from-record instance slot :database database) + (let ((vd (choose-database-for-instance instance database))) + (setf (slot-value instance 'view-database) vd) + (update-slot-from-db-value instance slot-def res)))) + + +(defvar +no-slot-value+ '+no-slot-value+) + +(defsql sql-slot-value (:symbol "slot-value") (classname slot &optional (value +no-slot-value+) (database *default-database*)) + (let* ((class (find-class classname)) + (sld (slotdef-for-slot-with-class slot class))) + (if sld + (if (eq value +no-slot-value+) + (sql-expression :attribute (database-identifier sld database) + :table (view-table class)) + (db-value-from-slot + sld + value + database)) + (error "Unknown slot ~A for class ~A" slot classname)))) + +(defsql sql-view-class (:symbol "view-class") (classname &optional (database *default-database*)) + (declare (ignore database)) + (let* ((class (find-class classname))) + (unless (view-table class) + (error "No view-table for class ~A" classname)) + (sql-expression :table (view-table class)))) + +(deftype tinyint () + "An 8-bit integer, this width may vary by SQL implementation." + 'integer) + +(deftype smallint () + "An integer smaller than a 32-bit integer. this width may vary by SQL implementation." + 'integer) + +(deftype mediumint () + "An integer smaller than a 32-bit integer, but may be larger than a smallint. This width may vary by SQL implementation." + 'integer) + +(deftype bigint () + "An integer larger than a 32-bit integer, this width may vary by SQL implementation." + 'integer) + +(deftype varchar (&optional size) + "A variable length string for the SQL varchar type." + (declare (ignore size)) + 'string) + +(deftype universal-time () + "A positive integer as returned by GET-UNIVERSAL-TIME." + '(integer 1 *)) + +(deftype generalized-boolean () + "A type which outputs a SQL boolean value, though any lisp type can be stored in the slot." + t) + +#+ignore +(deftype char (&optional len) + "A lisp type for the SQL CHAR type." + `(string ,len)) + +(defmethod database-get-type-specifier ((type string) args database (db-type t)) + "Pass through the literal type as defined in the type string" + (declare (ignore args database db-type)) + type) + + +(defmethod database-get-type-specifier ((type symbol) args database db-type) + (case type + (char (if args + (format nil "CHAR(~D)" (first args)) + "CHAR(1)")) + ((varchar string symbol keyword) + (if args + (format nil "VARCHAR(~A)" (car args)) + (format nil "VARCHAR(~D)" *default-string-length*))) + ((longchar text) "text") + (integer (if args + (format nil "INT(~A)" (car args)) + "INT")) + ((tinyint smallint mediumint) "INT") + ((long-float float) + (if args + (format nil "FLOAT(~A)" (car args)) + "FLOAT")) + ((bigint universal-time) "BIGINT") + (number + (cond + ((and (consp args) (= (length args) 2)) + (format nil "NUMBER(~D,~D)" (first args) (second args))) + ((and (consp args) (= (length args) 1)) + (format nil "NUMBER(~D)" (first args))) + (t + "NUMBER"))) + (wall-time "TIMESTAMP") + (date "DATE") + (duration "VARCHAR") + (money "INT8") + ((boolean generalized-boolean) "BOOL") + (t (warn "Could not determine a valid ~A type specifier for ~A ~A ~A, defaulting to VARCHAR " + db-type type args database) + (format nil "VARCHAR(~D)" *default-string-length*)))) + +(defun print-readable-symbol (in &aux (*package* (find-package :keyword)) + (*print-readably* t)) + (prin1-to-string in)) + +(defmethod database-output-sql-as-type + (type val database db-type + &aux + (*print-circle* t) (*print-array* t) + (*print-length* nil) (*print-base* #10r10)) + (declare (ignore database)) + (cond + ((null type) val) + ((member type '(boolean generalized-boolean)) + ;; booleans handle null differently + (case db-type + ;; done here so it can be done once + ((:mssql :mysql) (if val 1 0)) + (otherwise (if val "t" "f")))) + ((null val) + (when (next-method-p) + (call-next-method))) + (t + (case type + ((or symbol keyword) + (print-readable-symbol val)) + (string val) + (char (etypecase val + (character (write-to-string val)) + (string val))) + (float (format nil "~F" val)) + ((list vector array) + (prin1-to-string val)) + (otherwise + (if (next-method-p) + (call-next-method) + val)))))) + + +(defmethod read-sql-value :around + (val type database db-type + ;; never eval while reading values, always read base 10 + &aux *read-eval* (*read-base* #10r10)) + (declare (ignore db-type)) + (cond + ;; null value or type + ((or (null val) + (equalp "nil" val) + (eql 'null val) + (eql 'null type)) + nil) + + ;; no specified type or already the right type + ((or (null type) + (ignore-errors (typep val type))) + val) + + ;; actually convert + (t + (let ((res (handler-bind + ;; all errors should be converted to sql-value-conversion-error + ((error (lambda (c) + (unless (typep c 'sql-value-conversion-error) + ;; this was blowing up the tests till I + ;; unbound *debugger-hook* not sure the answer, + ;; as this is also imensely useful in actually + ;; finding bugs below this point + (when *debugger-hook* (invoke-debugger c)) + (error-converting-value val type database))))) + (call-next-method)))) + ;; if we didnt get the right type after converting, we should probably + ;; error right away + (maybe-error-converting-value res val type database))))) + +(defmethod read-sql-value (val type database db-type) + "read a sql value, from :around read-eval is disabled read numbers in base 10" + ;; errors, nulls and preconverted types are already handled in around + (typecase type + (symbol + (case type + ((string varchar) val) + (char (string (schar val 0))) + ((or keyword symbol) + (read-from-string val)) + ((smallint mediumint bigint integer universal-time) + (parse-integer val)) + ((double-float float) + ;; ensure that whatever we got is coerced to a float of the correct + ;; type (eg: 1=>1.0d0) + (float + (etypecase val + (string (let ((*read-default-float-format* + (ecase type + (float 'single-float) + (double-float 'double-float)))) + (read-from-string val))) + ;; maybe wrong type of float + (float val)) + (if (eql type 'double-float) 1.0d0 1.0s0))) + (number (read-decimal-value val)) + ((boolean generalized-boolean) + (if (member val '(nil t)) + val + (etypecase val + (string + (when (member val '("1" "t" "true" "y") :test #'string-equal) + t)) + (number (not (zerop val)))))) + ((wall-time duration) (parse-timestring val)) + (date (parse-datestring val)) + (list (read-from-string val)) + (t (error-converting-value val type database)))) + (t (typecase val + (string (read-from-string val)) + (t (error-converting-value val type database)))))) + +;; ------------------------------------------------------------ +;; Logic for 'faulting in' :join slots + +;; this works, but is inefficient requiring (+ 1 n-rows) +;; SQL queries +#+ignore +(defun fault-join-target-slot (class object slot-def) + (let* ((res (fault-join-slot-raw class object slot-def)) + (dbi (view-class-slot-db-info slot-def)) + (target-name (gethash :target-slot dbi)) + (target-class (find-class target-name))) + (when res + (mapcar (lambda (obj) + (list + (car + (fault-join-slot-raw + target-class + obj + (find target-name (class-slots (class-of obj)) + :key #'slot-definition-name))) + obj)) + res) + #+ignore ;; this doesn't work when attempting to call slot-value + (mapcar (lambda (obj) + (cons obj (slot-value obj ts))) res)))) + +(defun fault-join-target-slot (class object slot-def) + (let* ((dbi (view-class-slot-db-info slot-def)) + (ts (gethash :target-slot dbi)) + (jc (gethash :join-class dbi)) + (jc-view-table (view-table (find-class jc))) + (tdbi (view-class-slot-db-info + (find ts (class-slots (find-class jc)) + :key #'slot-definition-name))) + (retrieval (gethash :retrieval tdbi)) + (tsc (gethash :join-class tdbi)) + (ts-view-table (view-table (find-class tsc))) + (jq (join-qualifier class object slot-def)) + (key (slot-value object (gethash :home-key dbi)))) + + (when jq + (ecase retrieval + (:immediate + (let ((res + (find-all (list tsc) + :inner-join (sql-expression :table jc-view-table) + :on (sql-operation + '== + (sql-expression + :attribute (gethash :foreign-key tdbi) + :table ts-view-table) + (sql-expression + :attribute (gethash :home-key tdbi) + :table jc-view-table)) + :where jq + :result-types :auto + :database (choose-database-for-instance object)))) + (mapcar #'(lambda (i) + (let* ((instance (car i)) + (jcc (make-instance jc :view-database (choose-database-for-instance instance)))) + (setf (slot-value jcc (gethash :foreign-key dbi)) + key) + (setf (slot-value jcc (gethash :home-key tdbi)) + (slot-value instance (gethash :foreign-key tdbi))) + (list instance jcc))) + res))) + (:deferred + ;; just fill in minimal slots + (mapcar + #'(lambda (k) + (let ((instance (make-instance tsc :view-database (choose-database-for-instance object))) + (jcc (make-instance jc :view-database (choose-database-for-instance object))) + (fk (car k))) + (setf (slot-value instance (gethash :home-key tdbi)) fk) + (setf (slot-value jcc (gethash :foreign-key dbi)) + key) + (setf (slot-value jcc (gethash :home-key tdbi)) + fk) + (list instance jcc))) + (select (sql-expression :attribute (gethash :foreign-key tdbi) :table jc-view-table) + :from (sql-expression :table jc-view-table) + :where jq + :database (choose-database-for-instance object)))))))) + + +;;; Remote Joins + +(defvar *default-update-objects-max-len* nil + "The default value to use for the MAX-LEN keyword argument to + UPDATE-OBJECT-JOINS.") + +(defun %update-objects-joins-slot-defs (class slot-names) + "Get the slot definitions for the joins slots specified as slot-names + if slot-names is :immediate, :deferred or (or :all t) return all of + that type of slot definitions" + (setf class (to-class class)) + (when (eq t slot-names) (setf slot-names :all)) + (etypecase slot-names + (null nil) + (keyword + ;; slot-names is the retrieval type of the join-slot or :all + (get-join-slots class slot-names)) + ((or symbol list) + (loop for slot in (listify slot-names) + for def = (find-slot-by-name class slot) + when (and def (join-slot-p def)) + collecting def + unless (and def (join-slot-p def)) + do (warn "Unable to find join slot named ~S in class ~S." slot class))))) + +(defun get-joined-objects (objects slotdef &key force-p + (batch-size *default-update-objects-max-len*)) + "Given a list of objects and a join slot-def get the objects that need to be + joined to the input objects + + we will query in batches as large as batch-size" + (when (join-slot-p slotdef) + (let* ((slot-name (to-slot-name slotdef)) + (join-class (join-slot-class-name slotdef)) + (home-key (join-slot-info-value slotdef :home-key)) + (foreign-key (join-slot-info-value slotdef :foreign-key)) + (foreign-key-values + (remove-duplicates + (loop for object in (listify objects) + for hk = (slot-value object home-key) + when (or force-p + (not (slot-boundp object slot-name))) + collect hk) + :test #'equal))) + ;; we want to retrieve at most batch-size objects per query + (flet ((fetch (keys) + (find-all + (list join-class) + :where (make-instance + 'sql-relational-exp + :operator 'in + :sub-expressions (list (sql-expression :attribute foreign-key) + keys)) + :result-types :auto + :flatp t))) + (if (null batch-size) + (fetch foreign-key-values) + (loop + for keys = (pop-n foreign-key-values batch-size) + while keys + nconcing (fetch keys))))))) + +(defun %object-joins-from-list (object slot joins force-p ) + "Given a list of objects that we are trying to join to, pull the correct + ones for this object" + (when (or force-p (not (slot-boundp object (to-slot-name slot)))) + (let ((home-key (join-slot-info-value slot :home-key)) + (foreign-key (join-slot-info-value slot :foreign-key))) + (loop for join in joins + when (equal (slot-value join foreign-key) + (slot-value object home-key)) + collect join)))) + +(defun update-objects-joins (objects &key (slots :immediate) (force-p t) + class-name (max-len *default-update-objects-max-len*)) + "Updates from the records of the appropriate database tables the join slots + specified by SLOTS in the supplied list of View Class instances OBJECTS. + + A simpler method of causing a join-slot to be requeried is to set it to + unbound, then request it again. This function has efficiency gains where + join-objects are shared among the `objects` (querying all join-objects, + then attaching them appropriately to each of the `objects`) + + SLOTS can be one of: + + * :immediate (DEFAULT) - refresh join slots created with :retrieval :immediate + * :deferred - refresh join slots created with :retrieval :deferred + * :all,t - refresh all join slots regardless of :retrieval + * list of symbols - which explicit slots to refresh + * a single symobl - what slot to refresh + + CLASS-NAME is used to specify the View Class of all instance in OBJECTS and + default to nil which means that the class of the first instance in OBJECTS + is used. + + FORCE-P is t by default which means that all join slots are updated whereas + a value of nil means that only unbound join slots are updated. + + MAX-LEN defaults to *DEFAULT-UPDATE-OBJECTS-MAX-LEN* When non-nil this is + essentially a batch size for the max number of objects to query from the + database at a time. If we need more than max-len we loop till we have all + the objects" + (assert (or (null max-len) (plusp max-len))) + (when objects + (defaulting class-name (class-name (class-of (first objects)))) + (let* ((class (find-class class-name)) + (slotdefs (%update-objects-joins-slot-defs class slots))) + (loop for slotdef in slotdefs + ;; all the joins we will need for *all* the objects + ;; which then get filtered below for each object + for joins = (unless (join-slot-info-value slotdef :target-slot) + (get-joined-objects objects slotdef + :force-p force-p :batch-size max-len)) + do (loop for object in objects + for these-joins = ;; the joins just for this object (filtered from above) + ;; or retrieved via fault-join-target-slot + (or (%object-joins-from-list object slotdef joins force-p) + (when (join-slot-info-value slotdef :target-slot) + (fault-join-target-slot class object slotdef))) + ;; when this object has joined-objects copy them in to the correct slot + do (when these-joins + (setf (easy-slot-value object slotdef) + (if (join-slot-info-value slotdef :set) + these-joins + (first these-joins)))))))) + (values)) + +(defun fault-join-slot-raw (class object slot-def) + (let* ((dbi (view-class-slot-db-info slot-def)) + (jc (gethash :join-class dbi))) + (let ((jq (join-qualifier class object slot-def))) + (when jq + (select jc :where jq :flatp t :result-types nil + :database (choose-database-for-instance object)))))) + +(defun fault-join-slot (class object slot-def) + (let* ((dbi (view-class-slot-db-info slot-def)) + (ts (gethash :target-slot dbi)) + (dbi-set (gethash :set dbi))) + (if (and ts dbi-set) + (fault-join-target-slot class object slot-def) + (let ((res (fault-join-slot-raw class object slot-def))) + (when res + (cond + ((and ts (not dbi-set)) + (mapcar (lambda (obj) (slot-value obj ts)) res)) + ((and (not ts) (not dbi-set)) + (car res)) + ((and (not ts) dbi-set) + res))))))) + +(defun update-fault-join-normalized-slot (class object slot-def) + (if (and (normalizedp class) (key-slot-p slot-def)) + (setf (easy-slot-value object slot-def) + (normalized-key-value object)) + (update-slot-from-record object slot-def))) + +(defun all-home-keys-have-values-p (object slot-def) + "Do all of the home-keys have values ?" + (let ((home-keys (join-slot-info-value slot-def :home-key))) + (loop for key in (listify home-keys) + always (easy-slot-value object key)))) + +(defun join-qualifier (class object slot-def) + "Builds the join where clause based on the keys of the join slot and values + of the object" + (declare (ignore class)) + (let* ((jc (join-slot-class slot-def)) + ;;(ts (gethash :target-slot dbi)) + ;;(tsdef (if ts (slotdef-for-slot-with-class ts jc))) + (foreign-keys (listify (join-slot-info-value slot-def :foreign-key))) + (home-keys (listify (join-slot-info-value slot-def :home-key)))) + (when (all-home-keys-have-values-p object slot-def) + (clsql-ands + (loop for hk in home-keys + for fk in foreign-keys + for fksd = (slotdef-for-slot-with-class fk jc) + for fk-sql = (typecase fk + (symbol + (sql-expression + :attribute (database-identifier fksd nil) + :table (database-identifier jc nil))) + (t fk)) + for hk-val = (typecase hk + ((or symbol + view-class-effective-slot-definition + view-class-direct-slot-definition) + (easy-slot-value object hk)) + (t hk)) + collect (sql-operation '== fk-sql hk-val)))))) + +(defmethod select-table-sql-expr ((table T)) + "Turns an object representing a table into the :from part of the sql expression that will be executed " + (sql-expression :table (view-table table))) + +(defun select-reference-equal (r1 r2) + "determines if two sql select references are equal + using database identifier equal" + (flet ((id-of (r) + (etypecase r + (cons (cdr r)) + (sql-ident-attribute r)))) + (database-identifier-equal (id-of r1) (id-of r2)))) + +(defun join-slot-qualifier (class join-slot) + "Creates a sql-expression expressing the join between the home-key on the table + and its respective key on the joined-to-table" + (sql-operation + '== + (sql-expression + :attribute (join-slot-info-value join-slot :foreign-key) + :table (view-table (join-slot-class join-slot))) + (sql-expression + :attribute (join-slot-info-value join-slot :home-key) + :table (view-table class)))) + +(defun all-immediate-join-classes-for (classes) + "returns a list of all join-classes needed for a list of classes" + (loop for class in (listify classes) + appending (loop for slot in (immediate-join-slots class) + collect (join-slot-class slot)))) + +(defun %tables-for-query (classes from where inner-joins) + "Given lists of classes froms wheres and inner-join compile a list + of tables that should appear in the FROM section of the query. + + This includes any immediate join classes from each of the classes" + (let ((inner-join-tables (collect-table-refs (listify inner-joins)))) + (loop for tbl in (append + (mapcar #'select-table-sql-expr classes) + (mapcar #'select-table-sql-expr + (all-immediate-join-classes-for classes)) + (collect-table-refs (listify where)) + (collect-table-refs (listify from))) + when (and tbl + (not (find tbl rtn :test #'database-identifier-equal)) + ;; TODO: inner-join is currently hacky as can be + (not (find tbl inner-join-tables :test #'database-identifier-equal))) + collect tbl into rtn + finally (return rtn)))) + + +(defclass select-list () + ((view-class :accessor view-class :initarg :view-class :initform nil) + (select-list :accessor select-list :initarg :select-list :initform nil) + (slot-list :accessor slot-list :initarg :slot-list :initform nil) + (joins :accessor joins :initarg :joins :initform nil) + (join-slots :accessor join-slots :initarg :join-slots :initform nil)) + (:documentation + "Collects the classes, slots and their respective sql representations + so that update-instance-from-recors, find-all, build-objects can share this + info and calculate it once. Joins are select-lists for each immediate join-slot + but only if make-select-list is called with do-joins-p")) + +(defmethod view-table ((o select-list)) + (view-table (view-class o))) + +(defmethod sql-table ((o select-list)) + (sql-expression :table (view-table o))) + +(defmethod filter-select-list ((c clsql-sys::standard-db-object) + (sl clsql-sys::select-list) + database) + sl) + +(defun make-select-list (class-and-slots &key (do-joins-p nil) + (database *default-database*)) + "Make a select-list for the current class (or class-and-slots) object." + (let* ((class-and-slots + (etypecase class-and-slots + (class-and-slots class-and-slots) + ((or symbol standard-db-class) + ;; find the first class with slots for us to select (this should be) + ;; the first of its classes / parent-classes with slots + (first (reverse (view-classes-and-storable-slots + (to-class class-and-slots) + :to-database-p nil)))))) + (class (view-class class-and-slots)) + (join-slots (when do-joins-p (immediate-join-slots class)))) + (multiple-value-bind (slots sqls) + (loop for slot in (slot-defs class-and-slots) + for sql = (generate-attribute-reference class slot) + collect slot into slots + collect sql into sqls + finally (return (values slots sqls))) + (unless slots + (error "No slots of type :base in view-class ~A" (class-name class))) + (let ((sl (make-instance + 'select-list + :view-class class + :select-list sqls + :slot-list slots + :join-slots join-slots + ;; only do a single layer of join objects + :joins (when do-joins-p + (loop for js in join-slots + collect (make-select-list + (join-slot-class js) + :do-joins-p nil + :database database)))))) + (filter-select-list (make-instance class) sl database) + sl)))) + +(defun full-select-list ( select-lists ) + "Returns a list of sql-ref of things to select for the given classes + + THIS NEEDS TO MATCH THE ORDER OF build-objects + " + (loop for s in (listify select-lists) + appending (select-list s) + appending (loop for join in (joins s) + appending (select-list join)))) + +(defun build-objects (select-lists row database &optional existing-instances) + "Used by find-all to build objects. + + THIS NEEDS TO MATCH THE ORDER OF FULL-SELECT-LIST + + TODO: this caching scheme seems bad for a number of reasons + * order is not guaranteed so references being held by one object + might change to represent a different database row (seems HIGHLY + suspect) + * also join objects are overwritten rather than refreshed + + TODO: the way we handle immediate joins seems only valid if it is a single + object. I suspect that making a :set :immediate join column would result + in an invalid number of objects returned from the database, because there + would be multiple rows per object, but we would return an object per row + " + (setf existing-instances (listify existing-instances)) + (loop + for select-list in select-lists + for class = (view-class select-list) + for existing = (pop existing-instances) + for object = (or existing + (make-instance class :view-database database)) + do (loop for slot in (slot-list select-list) + do (update-slot-from-db-value object slot (pop row))) + do (loop for join-slot in (join-slots select-list) + for join in (joins select-list) + for join-class = (view-class join) + for join-object = + (setf (easy-slot-value object join-slot) + (make-instance join-class)) + do (loop for slot in (slot-list join) + do (update-slot-from-db-value join-object slot (pop row)))) + do (when existing (instance-refreshed object)) + collect object)) + +(defun find-all (view-classes + &rest args + &key all set-operation distinct from where group-by having + order-by offset limit refresh flatp result-types + inner-join on + (database *default-database*) + instances parameters) + "Called by SELECT to generate object query results when the + View Classes VIEW-CLASSES are passed as arguments to SELECT. + + TODO: the caching scheme of passing in instances and overwriting their + values seems bad for a number of reasons + * order is not guaranteed so references being held by one object + might change to represent a different database row (seems HIGHLY + suspect) + + TODO: the way we handle immediate joins seems only valid if it is a single + object. I suspect that making a :set :immediate join column would result + in an invalid number of objects returned from the database, because there + would be multiple objects returned from the database + " + (declare (ignore all set-operation group-by having offset limit on parameters + distinct order-by) + (dynamic-extent args)) + (let* ((args (filter-plist + args :from :where :flatp :additional-fields :result-types :instances)) + (*db-deserializing* t) + (sclasses (mapcar #'to-class view-classes)) + (tables (%tables-for-query sclasses from where inner-join)) + (join-where + (loop for class in sclasses + appending (loop for slot in (immediate-join-slots class) + collect (join-slot-qualifier class slot)))) + (select-lists (loop for class in sclasses + collect (make-select-list class :do-joins-p t :database database))) + (full-select-list (full-select-list select-lists)) + (where (clsql-ands (append (listify where) (listify join-where)))) + #| + (_ (format t "~&sclasses: ~W~%ijc: ~W~%tables: ~W~%" + sclasses immediate-join-classes tables)) + |# + (rows (apply #'select + (append full-select-list + (list :from tables + :result-types result-types + :where where) + args))) + (return-objects + (loop for row in rows + for old-objs = (pop instances) + for objs = (build-objects select-lists row database + (when refresh old-objs)) + collecting (if flatp + (delist-if-single objs) + objs)))) + return-objects)) + +(defmethod instance-refreshed ((instance standard-db-object))) + +(defvar *default-caching* t + "Controls whether SELECT caches objects by default. The CommonSQL +specification states caching is on by default.") + +(defun select (&rest select-all-args) + "Executes a query on DATABASE, which has a default value of +*DEFAULT-DATABASE*, specified by the SQL expressions supplied +using the remaining arguments in SELECT-ALL-ARGS. The SELECT +argument can be used to generate queries in both functional and +object oriented contexts. + +In the functional case, the required arguments specify the +columns selected by the query and may be symbolic SQL expressions +or strings representing attribute identifiers. Type modified +identifiers indicate that the values selected from the specified +column are converted to the specified lisp type. The keyword +arguments ALL, DISTINCT, FROM, GROUP-by, HAVING, ORDER-BY, +SET-OPERATION and WHERE are used to specify, using the symbolic +SQL syntax, the corresponding components of the SQL query +generated by the call to SELECT. RESULT-TYPES is a list of +symbols which specifies the lisp type for each field returned by +the query. If RESULT-TYPES is nil all results are returned as +strings whereas the default value of :auto means that the lisp +types are automatically computed for each field. FIELD-NAMES is t +by default which means that the second value returned is a list +of strings representing the columns selected by the query. If +FIELD-NAMES is nil, the list of column names is not returned as a +second value. + +In the object oriented case, the required arguments to SELECT are +symbols denoting View Classes which specify the database tables +to query. In this case, SELECT returns a list of View Class +instances whose slots are set from the attribute values of the +records in the specified table. Slot-value is a legal operator +which can be employed as part of the symbolic SQL syntax used in +the WHERE keyword argument to SELECT. REFRESH is nil by default +which means that the View Class instances returned are retrieved +from a cache if an equivalent call to SELECT has previously been +issued. If REFRESH is true, the View Class instances returned are +updated as necessary from the database and the generic function +INSTANCE-REFRESHED is called to perform any necessary operations +on the updated instances. + +In both object oriented and functional contexts, FLATP has a +default value of nil which means that the results are returned as +a list of lists. If FLATP is t and only one result is returned +for each record selected in the query, the results are returned +as elements of a list." + (multiple-value-bind (target-args qualifier-args) + (query-get-selections select-all-args) + (unless (or *default-database* (getf qualifier-args :database)) + (signal-no-database-error nil)) + + (let ((caching (getf qualifier-args :caching *default-caching*)) + (result-types (getf qualifier-args :result-types :auto)) + (refresh (getf qualifier-args :refresh nil)) + (database (getf qualifier-args :database *default-database*))) + + (cond + ((and target-args + (every #'(lambda (arg) + (and (symbolp arg) + (find-class arg nil))) + target-args)) + + (setf qualifier-args (filter-plist qualifier-args :caching :refresh :result-types)) + + ;; Add explicity table name to order-by if not specified and only + ;; one selected table. This is required so FIND-ALL won't duplicate + ;; the field + (let ((order-by (getf qualifier-args :order-by))) + (when (and order-by (= 1 (length target-args))) + (let ((table-name (view-table (find-class (car target-args)))) + (order-by-list (copy-seq (listify order-by)))) + (labels ((sv (val name) (ignore-errors (slot-value val name))) + (set-table-if-needed (val) + (typecase val + (sql-ident-attribute + (handler-case + (if (sv val 'qualifier) + val + (make-instance 'sql-ident-attribute + :name (sv val 'name) + :qualifier table-name)) + (simple-error () + ;; TODO: Check for a specific error we expect + ))) + (cons (cons (set-table-if-needed (car val)) + (cdr val))) + (t val)))) + (setf order-by-list + (loop for i from 0 below (length order-by-list) + for id in order-by-list + collect (set-table-if-needed id)))) + (setf (getf qualifier-args :order-by) order-by-list)))) + + (cond + ((null caching) + (apply #'find-all target-args :result-types result-types :refresh refresh qualifier-args)) + (t + (let ((cached (records-cache-results target-args qualifier-args database))) + (if (and cached (not refresh)) + cached + (let ((results (apply #'find-all target-args + :result-types :auto :refresh refresh + :instances cached + qualifier-args))) + (setf (records-cache-results target-args qualifier-args database) results) + + results)))))) + (t + (let* ((expr (apply #'make-query select-all-args)) + (parameters (second (member :parameters select-all-args))) + (specified-types + (mapcar #'(lambda (attrib) + (if (typep attrib 'sql-ident-attribute) + (let ((type (slot-value attrib 'type))) + (if type + type + t)) + t)) + (slot-value expr 'selections))) + (flatp (getf qualifier-args :flatp)) + (field-names (getf qualifier-args :field-names t))) + + (when parameters + (setf expr (command-object (sql-output expr database) parameters))) + (query expr :flatp flatp + :result-types + ;; specifying a type for an attribute overrides result-types + (if (some #'(lambda (x) (not (eq t x))) specified-types) + specified-types + result-types) + :field-names field-names + :database database))))))) + +(defun compute-records-cache-key (targets qualifiers) + (list targets + (do ((args *select-arguments* (cdr args)) + (results nil)) + ((null args) results) + (let* ((arg (car args)) + (value (getf qualifiers arg))) + (when value + (push (list arg + (typecase value + (cons (cons (sql (car value)) (cdr value))) + (%sql-expression (sql value)) + (t value))) + results)))))) + +(defun records-cache-results (targets qualifiers database) + (when (record-caches database) + (gethash (compute-records-cache-key targets qualifiers) (record-caches database)))) + +(defun (setf records-cache-results) (results targets qualifiers database) + (unless (record-caches database) + (setf (record-caches database) + (make-weak-hash-table :test 'equal))) + (setf (gethash (compute-records-cache-key (copy-list targets) qualifiers) + (record-caches database)) results) + results) + + + +;;; Serialization functions + +(defun write-instance-to-stream (obj stream) + "Writes an instance to a stream where it can be later be read. +NOTE: an error will occur if a slot holds a value which can not be written readably." + (let* ((class (class-of obj)) + (alist '())) + (dolist (slot (ordered-class-slots (class-of obj))) + (let ((name (slot-definition-name slot))) + (when (and (not (eq 'view-database name)) + (slot-boundp obj name)) + (push (cons name (slot-value obj name)) alist)))) + (setq alist (reverse alist)) + (write (cons (class-name class) alist) :stream stream :readably t)) + obj) + +(defun read-instance-from-stream (stream) + (let ((raw (read stream nil nil))) + (when raw + (let ((obj (make-instance (car raw)))) + (dolist (pair (cdr raw)) + (setf (slot-value obj (car pair)) (cdr pair))) + obj)))) diff --git a/sql/operations.lisp b/sql/operations.lisp new file mode 100644 index 0000000..faa22b5 --- /dev/null +++ b/sql/operations.lisp @@ -0,0 +1,262 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Definition of SQL operations used with the symbolic SQL syntax. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +;; Keep a hashtable for mapping symbols to sql generator functions, +;; for use by the bracketed reader syntax. + +(defvar *sql-op-table* (make-hash-table :test #'equal)) + + +;; Define an SQL operation type. + +(defmacro defsql (function definition-keys &body body) + `(progn + (defun ,function ,@body) + (let ((symbol (cadr (member :symbol ',definition-keys)))) + (setf (gethash (if symbol (symbol-name-default-case symbol) ',function) + *sql-op-table*) + ',function)))) + + +;; SQL operations + +(defsql sql-query (:symbol "select") (&rest args) + (apply #'make-query args)) + +(defsql sql-any (:symbol "any") (&rest rest) + (make-instance 'sql-function-exp + :name 'any :args rest)) + +(defsql sql-some (:symbol "some") (&rest rest) + (make-instance 'sql-function-exp + :name 'some :args rest)) + +(defsql sql-all (:symbol "all") (&rest rest) + (make-instance 'sql-function-exp + :name 'all :args rest)) + +(defsql sql-e-string (:symbol "E") (&rest rest) + (make-instance 'sql-escape-string-exp :string (first rest))) + +(defsql sql-not (:symbol "not") (&rest rest) + (make-instance 'sql-value-exp + :modifier 'not :components rest)) + +(defsql sql-union (:symbol "union") (&rest rest) + (make-instance 'sql-set-exp + :operator 'union :sub-expressions rest)) + +(defsql sql-intersect (:symbol "intersect") (&rest rest) + (make-instance 'sql-set-exp + :operator 'intersect :sub-expressions rest)) + +(defsql sql-except (:symbol "except") (&rest rest) + (make-instance 'sql-set-exp + :operator 'except :sub-expressions rest)) + +(defsql sql-minus (:symbol "minus") (&rest rest) + (make-instance 'sql-set-exp + :operator 'minus :sub-expressions rest)) + +(defsql sql-limit (:symbol "limit") (&rest rest) + (make-instance 'sql-query-modifier-exp + :modifier 'limit :components rest)) + +(defsql sql-group-by (:symbol "group-by") (&rest rest) + (make-instance 'sql-query-modifier-exp + :modifier '|GROUP BY| :components rest)) + +(defsql sql-order-by (:symbol "order-by") (&rest rest) + (make-instance 'sql-query-modifier-exp + :modifier '|ORDER BY| :components rest)) + +(defsql sql-having (:symbol "having") (&rest rest) + (make-instance 'sql-query-modifier-exp + :modifier 'having :components rest)) + +(defsql sql-null (:symbol "null") (&optional not-null-thing) + (if not-null-thing + (make-instance 'sql-relational-exp :operator 'is + :sub-expressions (list not-null-thing nil)) + (make-instance 'sql-value-exp :components 'null))) + +(defsql sql-not-null (:symbol "not-null") (&optional not-null-thing) + (if not-null-thing + (make-instance + 'sql-relational-exp + :operator 'IS + :sub-expressions (list not-null-thing + (sql-expression :string "NOT NULL"))) + (sql-expression :string "NOT NULL"))) + +(defsql sql-exists (:symbol "exists") (&rest rest) + (make-instance 'sql-function-exp + :name 'exists :args rest)) + +(defsql sql-* (:symbol "*") (&rest rest) + (if (zerop (length rest)) + (make-instance 'sql-ident :name '*) + (make-instance 'sql-relational-exp :operator '* :sub-expressions rest))) + +(defsql sql-+ (:symbol "+") (&rest rest) + (if (cdr rest) + (make-instance 'sql-relational-exp + :operator '+ :sub-expressions rest) + (make-instance 'sql-value-exp :modifier '+ :components rest))) + +(defsql sql-/ (:symbol "/") (&rest rest) + (make-instance 'sql-relational-exp + :operator '/ :sub-expressions rest)) + +(defsql sql-- (:symbol "-") (&rest rest) + (if (cdr rest) + (make-instance 'sql-relational-exp + :operator '- :sub-expressions rest) + (make-instance 'sql-value-exp :modifier '- :components rest))) + +(defsql sql-like (:symbol "like") (&rest rest) + (make-instance 'sql-relational-exp + :operator 'like :sub-expressions rest)) + +(defsql sql-uplike (:symbol "uplike") (&rest rest) + (make-instance 'sql-upcase-like + :sub-expressions rest)) + +(defsql sql-and (:symbol "and") (&rest rest) + (make-instance 'sql-relational-exp + :operator 'and :sub-expressions rest)) + +(defsql sql-or (:symbol "or") (&rest rest) + (make-instance 'sql-relational-exp + :operator 'or :sub-expressions rest)) + +(defsql sql-in (:symbol "in") (&rest rest) + (make-instance 'sql-relational-exp + :operator 'in :sub-expressions rest)) + +(defsql sql-concat-op (:symbol "concat-op") (&rest rest) + (make-instance 'sql-relational-exp + :operator '\|\| :sub-expressions rest)) + +(defsql sql-concat (:symbol "concat") (&rest rest) + (make-instance 'sql-function-exp + :name 'concat :args rest)) + +(defsql sql-substr (:symbol "substr") (&rest rest) + (if (= (length rest) 3) + (make-instance 'sql-function-exp + :name 'substr :args rest) + (error 'sql-user-error :message "SUBSTR must have 3 arguments."))) + +(defsql sql-substring (:symbol "substring") (&rest rest) + (if (= (length rest) 3) + (make-instance 'sql-function-exp + :name 'substring :args rest) + (error 'sql-user-error :message "SUBSTRING must have 3 arguments."))) + +(defsql sql-is (:symbol "is") (&rest rest) + (make-instance 'sql-relational-exp + :operator 'is :sub-expressions rest)) + +(defsql sql-= (:symbol "=") (&rest rest) + (make-instance 'sql-relational-exp + :operator '= :sub-expressions rest)) + +(defsql sql-== (:symbol "==") (&rest rest) + (make-instance 'sql-assignment-exp + :operator '= :sub-expressions rest)) + +(defsql sql-< (:symbol "<") (&rest rest) + (make-instance 'sql-relational-exp + :operator '< :sub-expressions rest)) + + +(defsql sql-> (:symbol ">") (&rest rest) + (make-instance 'sql-relational-exp + :operator '> :sub-expressions rest)) + +(defsql sql-<> (:symbol "<>") (&rest rest) + (make-instance 'sql-relational-exp + :operator '<> :sub-expressions rest)) + +(defsql sql->= (:symbol ">=") (&rest rest) + (make-instance 'sql-relational-exp + :operator '>= :sub-expressions rest)) + +(defsql sql-<= (:symbol "<=") (&rest rest) + (make-instance 'sql-relational-exp + :operator '<= :sub-expressions rest)) + +(defsql sql-count (:symbol "count") (&rest rest) + (make-instance 'sql-function-exp + :name 'count :args rest)) + +(defsql sql-max (:symbol "max") (&rest rest) + (make-instance 'sql-function-exp + :name 'max :args rest)) + +(defsql sql-min (:symbol "min") (&rest rest) + (make-instance 'sql-function-exp + :name 'min :args rest)) + +(defsql sql-avg (:symbol "avg") (&rest rest) + (make-instance 'sql-function-exp + :name 'avg :args rest)) + +(defsql sql-sum (:symbol "sum") (&rest rest) + (make-instance 'sql-function-exp + :name 'sum :args rest)) + +(defsql sql-the (:symbol "the") (&rest rest) + (make-instance 'sql-typecast-exp + :modifier (first rest) :components (second rest))) + +(defsql sql-function (:symbol "function") (&rest args) + (make-instance 'sql-function-exp + :name (make-symbol (car args)) :args (cdr args))) + +(defsql sql-between (:symbol "between") (&rest rest) + (if (= (length rest) 3) + (make-instance 'sql-between-exp :name 'between :args rest) + (error 'sql-user-error :message "BETWEEN must have 3 arguments."))) + +(defsql sql-distinct (:symbol "distinct") (&rest rest) + (make-instance 'sql-query-modifier-exp :modifier 'distinct + :components rest)) + +(defsql sql-coalesce (:symbol "coalesce") (&rest rest) + (make-instance 'sql-function-exp + :name 'coalesce :args rest)) + +(defsql sql-nvl (:symbol "nvl") (&rest rest) + (if (= (length rest) 2) + (make-instance 'sql-function-exp + :name 'coalesce :args rest) + (error 'sql-user-error :message "NVL accepts exactly 2 arguments."))) + +(defsql sql-userenv (:symbol "userenv") (&rest rest) + (make-instance 'sql-function-exp + :name 'userenv :args rest)) + +(defsql sql-lower (:symbol "lower") (&rest rest) + (if (= (length rest) 1) + (make-instance 'sql-function-exp + :name 'lower :args rest) + (error 'sql-user-error :message "LOWER must have 1 argument."))) + +(defsql sql-upper (:symbol "upper") (&rest rest) + (if (= (length rest) 1) + (make-instance 'sql-function-exp + :name 'upper :args rest) + (error 'sql-user-error :message "UPPER must have 1 argument."))) diff --git a/sql/package.lisp b/sql/package.lisp new file mode 100644 index 0000000..8915b06 --- /dev/null +++ b/sql/package.lisp @@ -0,0 +1,621 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: package.lisp +;;;; Purpose: Package definition for SQL interface +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +;;;; This file makes the required package definitions for CLSQL's +;;;; core packages. + +(eval-when (:compile-toplevel :load-toplevel :execute) + #+sbcl + (if (find-package 'sb-mop) + (pushnew :clsql-sbcl-mop cl:*features*) + (pushnew :clsql-sbcl-pcl cl:*features*)) + + #+cmu + (if (eq (symbol-package 'pcl:find-class) + (find-package 'common-lisp)) + (pushnew :clsql-cmucl-mop cl:*features*) + (pushnew :clsql-cmucl-pcl cl:*features*))) + + +(eval-when (:compile-toplevel :load-toplevel :execute) + (defpackage #:clsql-sys + (:use #:common-lisp + #+clsql-sbcl-mop #:sb-mop + #+clsql-cmucl-mop #:mop + #+allegro #:mop + #+clisp #:clos + #+ecl #:mop + #+lispworks #:clos + #+scl #:clos + #+openmcl #:openmcl-mop) + + #+allegro + (:shadowing-import-from + #:excl) + #+clisp + (:shadowing-import-from + #:clos) + #+lispworks + (:shadowing-import-from + #:clos) + #+clsql-sbcl-mop + (:shadowing-import-from + #:sb-pcl + #:generic-function-lambda-list) + #+clsql-sbcl-pcl + (:shadowing-import-from + #:sb-pcl + #:name + #:class-direct-slots + #:class-of #:class-name #:class-slots #:find-class + #:slot-boundp + #:standard-class + #:slot-definition-name #:finalize-inheritance + #:standard-direct-slot-definition + #:standard-effective-slot-definition #:validate-superclass + #:direct-slot-definition-class #:compute-effective-slot-definition + #:effective-slot-definition-class + #:slot-value-using-class + #:class-prototype #:generic-function-method-class #:intern-eql-specializer + #:make-method-lambda #:generic-function-lambda-list + #:class-precedence-list #:slot-definition-type + #:class-direct-superclasses + #:compute-class-precedence-list) + #+clsql-cmucl-mop + (:shadowing-import-from + #:pcl + #:generic-function-lambda-list) + #+clsql-cmucl-pcl + (:shadowing-import-from + #:pcl + #:class-direct-slots + #:name + #:class-of #:class-name #:class-slots #:find-class #:standard-class + #:slot-boundp + #:slot-definition-name #:finalize-inheritance + #:standard-direct-slot-definition #:standard-effective-slot-definition + #:validate-superclass #:direct-slot-definition-class + #:effective-slot-definition-class + #:compute-effective-slot-definition + #:slot-value-using-class + #:class-prototype #:generic-function-method-class #:intern-eql-specializer + #:make-method-lambda #:generic-function-lambda-list + #:class-precedence-list #:slot-definition-type + #:class-direct-superclasses + #:compute-class-precedence-list) + #+scl + (:shadowing-import-from + #:clos + #:class-prototype ;; note: make-method-lambda is not fbound + ) + + (:export + ;; "Private" exports for use by interface packages + #:check-connection-spec + #:database-initialize-database-type + #:database-type-load-foreign + #:database-connect + #:database-disconnect + #:database-reconnect + #:database-query + #:database-execute-command + #:database-create-sequence + #:database-drop-sequence + #:database-sequence-next + #:database-set-sequence-position + #:database-query-result-set + #:database-dump-result-set + #:database-store-next-row + #:database-list-tables + #:database-list-tables-and-sequences + #:database-table-exists-p + #:database-list-views + #:database-view-exists-p + #:database-list-indexes + #:database-list-table-indexes + #:database-index-exists-p + #:database-list-sequences + #:database-sequence-last + #:database-sequence-exists-p + #:database-last-auto-increment-id + #:database-list-attributes + #:database-attribute-type + #:database-type-library-loaded + #:database-create + #:database-destroy + #:database-probe + #:database-list + #:database-acquire-from-conn-pool + #:database-release-to-conn-pool + + #:db-backend-has-create/destroy-db? + #:db-type-has-views? + #:db-type-has-bigint? + #:db-type-has-union? + #:db-type-has-prepared-stmt? + #:db-type-has-subqueries? + #:db-type-has-boolean-where? + #:db-type-transaction-capable? + #:db-type-has-fancy-math? + #:db-type-default-case + #:db-type-use-column-on-drop-index? + #:db-type-use-fully-qualified-column-on-drop-index? + #:db-type-has-intersect? + #:db-type-has-except? + #:db-type-has-auto-increment? + #:database-underlying-type + #:database-get-type-specifier + #:read-sql-value + #:database-output-sql-as-type + #:*loaded-database-types* + #:reload-database-types + #:is-database-open + #:*db-pool-max-free-connections* + + ;; Large objects + #:database-create-large-object + #:database-write-large-object + #:database-read-large-object + #:database-delete-large-object + #:create-large-object + #:write-large-object + #:read-large-object + #:delete-large-object + + ;; Prepared statments + #:database-prepare + #:database-run-prepared + #:database-bind-parameter + #:database-free-prepared + + ;; accessors for database class + #:name + #:connection-spec + #:transaction + #:transaction-level + #:conn-pool + #:command-recording-stream + #:result-recording-stream + #:record-caches + #:view-classes + #:database-state + #:attribute-cache + #:database-autocommit + #:encoding + + ;; utils.lisp + #:without-interrupts + #:make-process-lock + #:with-process-lock + #:command-output + #:symbol-name-default-case + #:convert-to-db-default-case + #:ensure-keyword + #:getenv + #:number-to-sql-string + #:float-to-sql-string + #:sql-escape-quotes + #:sql-escape + #:in + + ;; Command-object.lisp + #:expression + #:parameters + #:prepare-sql-parameter + #:prepared-name + #:has-been-prepared + #:command-object + #:reset-command-object + + ;; Generic backends + #:generic-postgresql-database + #:generic-odbc-database + + ;; Subclasses of sql-expresssion (expressions.lisp) + #:sql-function-exp + #:sql-value-exp + #:sql-set-exp + #:sql-query-modifier-exp + #:sql-relational-exp + #:sql-upcase-like + #:sql-assignment-exp + #:sql-typecast-exp + #:sql-between-exp + #:sql-ident + #:sql-ident-attribute + #:sql-ident-table + + . + + ;; Shared exports for re-export by CLSQL package. + #1=( + + ;; foreign library loading + #:*foreign-library-search-paths* + #:push-library-path + + ;; Condition system (conditions.lisp) + #:sql-user-error + #:sql-database-error + #:sql-database-data-error + #:sql-connection-error + #:sql-temporary-error + #:sql-timeout-error + #:sql-fatal-error + #:sql-error-error-id + #:sql-error-secondary-error-id + #:sql-error-database-message + ;; CLSQL Extensions + #:sql-condition + #:sql-error + #:sql-warning + #:sql-database-warning + #:sql-error-database + #:sql-error-database-type + #:sql-error-connection-spec + #:sql-error-expression + #:sql-warning-database + #:sql-user-error-message + #:*backend-warning-behavior* + + ;; Connection/initialisation (base-classes.lisp, database.lisp, + ;; initialize.lisp) + #:*default-database-type* + #:*default-database* + #:*initialized-database-types* + #:initialize-database-type + #:connect + #:disconnect + #:*connect-if-exists* + #:connected-databases + #:database + #:database-name + #:reconnect + #:find-database + #:status + ;; CLSQL Extensions + #:database-name-from-spec + #:database-type + #:with-database + #:with-default-database + #:disconnect-pooled + #:list-databases + #:create-database + #:destroy-database + #:probe-database + + ;; I/O Recording (recording.lisp) + #:add-sql-stream + #:delete-sql-stream + #:list-sql-streams + #:sql-recording-p + #:sql-stream + #:start-sql-recording + #:stop-sql-recording + + ;; FDDL (fddl.lisp) + #:create-table + #:drop-table + #:list-tables + #:table-exists-p + #:list-attributes + #:attribute-type + #:list-attribute-types + #:create-view + #:drop-view + #:create-index + #:drop-index + ;; CLSQL Extensions + #:truncate-database + #:list-views + #:view-exists-p + #:list-indexes + #:index-exists-p + #:create-sequence + #:drop-sequence + #:list-sequences + #:sequence-exists-p + #:sequence-next + #:sequence-last + #:set-sequence-position + #:*old-sequence-names* + + ;; FDML (fdml.lisp) + #:select + #:cache-table-queries + #:*cache-table-queries-default* + #:delete-records + #:insert-records + #:update-records + #:execute-command + #:query + #:print-query + #:do-query + #:map-query + #:loop + ;; CLSQL Extensions + #:prepare-sql + #:bind-parameter + #:run-prepared-sql + #:free-prepared-sql + + ;; Transaction handling (transaction.lisp) + #:with-transaction + #:commit + #:rollback + ;; CLSQL Extensions + #:add-transaction-commit-hook + #:add-transaction-rollback-hook + #:start-transaction + #:in-transaction-p + #:set-autocommit + + ;; OODDL (ooddl.lisp) + #:standard-db-object + #:def-view-class + #:create-view-from-class + #:drop-view-from-class + #:list-classes + #:universal-time + ;; CLSQL Extensions + #:view-table + #:bigint + #:varchar + #:longchar + #:text + #:generalized-boolean + #:mediumint + #:smallint + #:tinyint + #:*default-string-length* + + ;; OODML (oodml.lisp) + #:select-list + #:filter-select-list + #:slot-list + #:joins + #:join-slots + #:instance-refreshed + #:update-objects-joins + #:*default-update-objects-max-len* + #:*default-caching* + #:update-slot-from-record + #:update-instance-from-records + #:update-records-from-instance + #:update-record-from-slot + #:update-record-from-slots + #:delete-instance-records + ;; CLSQL Extensions + #:*db-auto-sync* + #:write-instance-to-stream + #:read-instance-from-stream + + ;; Symbolic SQL Syntax (syntax.lisp) + #:sql + #:sql-expression + #:sql-operation + #:sql-operator + #:disable-sql-reader-syntax + #:enable-sql-reader-syntax + #:locally-disable-sql-reader-syntax + #:locally-enable-sql-reader-syntax + #:restore-sql-reader-syntax-state + #:file-enable-sql-reader-syntax + + ;; SQL operations (operations.lisp) + #:sql-query + #:sql-object-query + #:sql-any + #:sql-some + #:sql-all + #:sql-not + #:sql-union + #:sql-intersect + #:sql-minus + #:sql-except + #:sql-order-by + #:sql-null + #:sql-* + #:sql-+ + #:sql-/ + #:sql-- + #:sql-like + #:sql-and + #:sql-or + #:sql-in + #:sql-substr + #:sql-concat-op + #:sql-= + #:sql-< + #:sql-> + #:sql->= + #:sql-<= + #:sql-<> + #:sql-count + #:sql-max + #:sql-min + #:sql-avg + #:sql-sum + #:sql-function + #:sql-between + #:sql-distinct + #:sql-nvl + #:sql-slot-value + #:sql-userenv + ;; CLSQL Extensions + #:sql-concat + #:sql-substring + #:sql-limit + #:sql-group-by + #:sql-having + #:sql-not-null + #:sql-exists + #:sql-uplike + #:sql-is + #:sql-== + #:sql-the + #:sql-coalesce + #:sql-view-class + + ;; Time handling (time.lisp) + #:bad-component + #:current-day + #:current-month + #:current-year + #:day-duration + #:db-timestring + #:db-datestring + #:decode-duration + #:decode-time + #:decode-date + #:duration + #:duration+ + #:duration< + #:duration<= + #:duration= + #:duration> + #:duration>= + #:duration-day + #:duration-hour + #:duration-minute + #:duration-month + #:duration-second + #:duration-year + #:duration-reduce + #:duration-timestring + #:extract-roman + #:format-duration + #:format-time + #:format-date + #:get-time + #:get-date + #:utime->time + #:interval-clear + #:interval-contained + #:interval-data + #:interval-edit + #:interval-end + #:interval-match + #:interval-push + #:interval-relation + #:interval-start + #:interval-type + #:make-duration + #:make-interval + #:make-time + #:make-date + #:merged-time + #:midnight + #:month-name + #:parse-date-time + #:parse-timestring + #:parse-datestring + #:parse-yearstring + #:print-date + #:roll + #:roll-to + #:time + #:time+ + #:time- + #:time-by-adding-duration + #:time-compare + #:time-difference + #:time-dow + #:time-element + #:time-max + #:time-min + #:time-mjd + #:time-msec + #:time-p + #:time-sec + #:time-well-formed + #:time-ymd + #:time< + #:time<= + #:time= + #:time> + #:time>= + #:date + #:date+ + #:date- + #:date-difference + #:date-compare + #:date-dow + #:date-element + #:date-max + #:date-min + #:date-mjd + #:date-p + #:date-ymd + #:date< + #:date<= + #:date= + #:date> + #:date>= + #:timezone + #:universal-time + #:wall-time + #:wall-timestring + #:week-containing + #:gregorian-to-mjd + #:mjd-to-gregorian + #:iso-timestring + )) + (:documentation "This is the INTERNAL SQL-Interface package of CLSQL.")) + + +(defpackage #:clsql + (:use #:common-lisp) + (:import-from #:clsql-sys . #1#) + (:export . #1#) + (:documentation "This is the user package with CLSQL symbols.")) + +(defpackage #:clsql-user + (:use #:common-lisp) + (:import-from #:clsql-sys . #1#) + (:export . #1#) + (:documentation "This is the user package with CLSQL symbols.")) + + ;; This is from USQL's pcl-patch + #+(or clsql-sbcl-pcl clsql-cmucl-pcl) + (progn + ;; Note that this will no longer required for cmucl as of version 19a. + (in-package #+cmu :pcl #+sbcl :sb-pcl) + (defmacro pv-binding1 ((pv calls pv-table-symbol pv-parameters slot-vars) + &body body) + `(pv-env (,pv ,calls ,pv-table-symbol ,pv-parameters) + (let (,@(mapcar #'(lambda (slot-var p) `(,slot-var (get-slots-or-nil ,p))) + slot-vars pv-parameters)) + ,@(mapcar #'(lambda (slot-var) `(declare (ignorable ,slot-var))) slot-vars) + ,@body)))) + +;; see http://thread.gmane.org/gmane.lisp.lispworks.general/681 +#+lispworks +(setf *packages-for-warn-on-redefinition* + (delete "SQL" *packages-for-warn-on-redefinition* :test 'string=)) + + #+sbcl + (if (find-package 'sb-mop) + (setq cl:*features* (delete :clsql-sbcl-mop cl:*features*)) + (setq cl:*features* (delete :clsql-sbcl-pcl cl:*features*))) + + #+cmu + (if (find-package 'mop) + (setq cl:*features* (delete :clsql-cmucl-mop cl:*features*)) + (setq cl:*features* (delete :clsql-cmucl-pcl cl:*features*))) + +) ;eval-when + diff --git a/sql/pool.lisp b/sql/pool.lisp new file mode 100644 index 0000000..a153c91 --- /dev/null +++ b/sql/pool.lisp @@ -0,0 +1,185 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: pool.lisp +;;;; Purpose: Support function for connection pool +;;;; Programmers: Kevin M. Rosenberg, Marc Battyani +;;;; Date Started: Apr 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defparameter *db-pool-max-free-connections* 4 + "Threshold of free-connections in the pool before we disconnect a database + rather than returning it to the pool. NIL for no limit. This is really a + heuristic that should, on avg keep the free connections about this size.") + +(defvar *db-pool* (make-hash-table :test #'equal)) +(defvar *db-pool-lock* (make-process-lock "DB Pool lock")) + +(defclass conn-pool () + ((connection-spec :accessor connection-spec :initarg :connection-spec) + (database-type :accessor pool-database-type :initarg :pool-database-type) + (free-connections :accessor free-connections :initform nil) + (all-connections :accessor all-connections :initform nil) + (lock :accessor conn-pool-lock + :initform (make-process-lock "Connection pool")))) + + + + +(defun acquire-from-pool (connection-spec database-type &optional pool (encoding *default-encoding*)) + "Try to find a working database connection in the pool or create a new +one if needed. This performs 1 query against the DB to ensure it's still +valid. When possible (postgres, mssql) that query will be a reset +command to put the connection back into its default state." + (unless (typep pool 'conn-pool) + (setf pool (find-or-create-connection-pool connection-spec database-type))) + (or + (loop for pconn = (with-process-lock ((conn-pool-lock pool) "Acquire") + (pop (free-connections pool))) + always pconn + thereis + ;; test if connection still valid. + ;; (e.g. db reboot -> invalid connection ) + (handler-case + (progn (database-acquire-from-conn-pool pconn) + pconn) + (sql-database-error (e) + ;; we could check for a specific error, + ;; but, it's safer just to disconnect the pooled conn for any error ? + (warn "Database connection ~S had an error while acquiring from the pool: + ~S +Disconnecting.~%" + pconn e) + ;;run database disconnect to give chance for cleanup + ;;there, then remove it from the lists of connected + ;;databases. + (%pool-force-disconnect pconn) + (with-process-lock ((conn-pool-lock pool) "remove dead conn") + (setf (all-connections pool) + (delete pconn (all-connections pool)))) + nil))) + (let ((conn (connect (connection-spec pool) + :database-type (pool-database-type pool) + :if-exists :new + :make-default nil + :encoding encoding))) + (setf (conn-pool conn) pool) + (with-process-lock ((conn-pool-lock pool) "new conection") + (push conn (all-connections pool))) + conn))) + +(defun release-to-pool (database &optional (pool (conn-pool database))) + "Release a database connection to the pool. The backend will have a +chance to do cleanup." + (unless (conn-pool database) (setf (conn-pool database) pool)) + (cond + ;;We read the list of free-connections outside the lock. This + ;;should be fine as long as that list is never dealt with + ;;destructively (push and pop destructively modify the place, + ;;not the list). Multiple threads getting to this test at the + ;;same time might result in the free-connections getting + ;;longer... meh. + ((or (and *db-pool-max-free-connections* + (>= (length (free-connections pool)) + *db-pool-max-free-connections*))) + (%pool-force-disconnect database) + + (with-process-lock ((conn-pool-lock pool) "Remove extra Conn") + (setf (all-connections pool) + (delete database (all-connections pool))))) + (t + ;;let it do cleanup + (database-release-to-conn-pool database) + (with-process-lock ((conn-pool-lock pool) "Release to pool") + (push database (free-connections pool)))))) + +(defmethod database-acquire-from-conn-pool (database) + (case (database-underlying-type database) + (:postgresql + (database-execute-command "RESET ALL" database)) + (:mysql + (database-query "SHOW ERRORS LIMIT 1" database nil nil)) + (:mssql + ;; rpc escape sequence since this can't be called as a normal sp. + ;;http://msdn.microsoft.com/en-us/library/aa198358%28SQL.80%29.aspx + (database-execute-command "{rpc sp_reset_connection}" database)) + (T + (database-query "SELECT 1;" database '(integer) nil)))) + +(defmethod database-release-to-conn-pool (database) + (case (database-underlying-type database) + (:postgresql + (ignore-errors + ;;http://www.postgresql.org/docs/current/static/sql-discard.html + ;;this was introduced relatively recently, wrap in ignore-errors + ;;so that it doesn't choke older versions. + (database-execute-command "DISCARD ALL" database))))) + +(defun clear-conn-pool (pool) + "Be careful this function will disconnect connections without regard +to whether another thread is actively using them." + (with-process-lock ((conn-pool-lock pool) "Clear pool") + (mapc #'%pool-force-disconnect (all-connections pool)) + (setf (all-connections pool) nil + (free-connections pool) nil)) + nil) + +(defun find-or-create-connection-pool (connection-spec database-type) + "Find connection pool in hash table, creates a new connection pool +if not found" + (let ((key (list connection-spec database-type))) + (with-process-lock (*db-pool-lock* "Find-or-create connection") + (or (gethash key *db-pool*) + (setf (gethash key *db-pool*) + (make-instance 'conn-pool + :connection-spec connection-spec + :pool-database-type database-type)))))) + +(defun disconnect-pooled (&optional clear) + "Disconnects all connections in the pool. When clear, also deletes +the pool objects." + (with-process-lock (*db-pool-lock* "Disconnect pooled") + (maphash + #'(lambda (key conn-pool) + (declare (ignore key)) + (clear-conn-pool conn-pool)) + *db-pool*) + (when clear (clrhash *db-pool*))) + t) + +(defun %pool-force-disconnect (database) + "Force disconnection of a connection from the pool." + ;;so it isn't just returned to pool + (setf (conn-pool database) nil) + ;; disconnect may error if remote side closed connection + (ignore-errors (disconnect :database database))) + +;(defun pool-start-sql-recording (pool &key (types :command)) +; "Start all stream in the pool recording actions of TYPES" +; (dolist (con (pool-connections pool)) +; (start-sql-recording :type types +; :database (connection-database con)))) + +;(defun pool-stop-sql-recording (pool &key (types :command)) +; "Start all stream in the pool recording actions of TYPES" +; (dolist (con (pool-connections pool)) +; (stop-sql-recording :type types +; :database (connection-database con)))) + +;(defmacro with-database-connection (pool &body body) +; `(let ((connection (obtain-connection ,pool)) +; (results nil)) +; (unwind-protect +; (with-database ((connection-database connection)) +; (setq results (multiple-value-list (progn ,@body)))) +; (release-connection connection)) +; (values-list results))) diff --git a/sql/recording.lisp b/sql/recording.lisp new file mode 100644 index 0000000..02bb972 --- /dev/null +++ b/sql/recording.lisp @@ -0,0 +1,165 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; CLSQL broadcast streams which can be used to monitor the +;;;; flow of commands to, and results from, a database. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defun start-sql-recording (&key (type :commands) (database *default-database*)) + "Starts recording of SQL commands sent to and/or results +returned from DATABASE which defaults to *DEFAULT-DATABASE*. The +SQL is output on one or more broadcast streams, initially just +*STANDARD-OUTPUT*, and the functions ADD-SQL-STREAM and +DELETE-SQL-STREAM may be used to add or delete command or result +recording streams. The default value of TYPE is :commands which +means that SQL commands sent to DATABASE are recorded. If TYPE +is :results then SQL results returned from DATABASE are +recorded. Both commands and results may be recorded by passing +TYPE value of :both." + (when (or (eq type :both) (eq type :commands)) + (setf (command-recording-stream database) + (make-broadcast-stream *standard-output*))) + (when (or (eq type :both) (eq type :results)) + (setf (result-recording-stream database) + (make-broadcast-stream *standard-output*))) + (values)) + +(defun stop-sql-recording (&key (type :commands) (database *default-database*)) + "Stops recording of SQL commands sent to and/or results +returned from DATABASE which defaults to *DEFAULT-DATABASE*. The +default value of TYPE is :commands which means that SQL commands +sent to DATABASE will no longer be recorded. If TYPE is :results +then SQL results returned from DATABASE will no longer be +recorded. Recording may be stopped for both commands and results +by passing TYPE value of :both." + (when (or (eq type :both) (eq type :commands)) + (setf (command-recording-stream database) nil)) + (when (or (eq type :both) (eq type :results)) + (setf (result-recording-stream database) nil)) + (values)) + +(defun sql-recording-p (&key (type :commands) (database *default-database*)) + "Predicate to test whether the SQL recording specified by TYPE +is currently enabled for DATABASE which defaults to *DEFAULT-DATABASE*. +TYPE may be one of :commands, :results, :both or :either, defaulting to +:commands, otherwise nil is returned." + (when (or (and (eq type :commands) + (command-recording-stream database)) + (and (eq type :results) + (result-recording-stream database)) + (and (eq type :both) + (result-recording-stream database) + (command-recording-stream database)) + (and (eq type :either) + (or (result-recording-stream database) + (command-recording-stream database)))) + t)) + +(defun add-sql-stream (stream &key (type :commands) + (database *default-database*)) + "Adds the supplied stream STREAM (or T for *standard-output*) +as a component of the recording broadcast stream for the SQL +recording type specified by TYPE on DATABASE which defaults to +*DEFAULT-DATABASE*. TYPE must be one of :commands, :results, +or :both, defaulting to :commands, depending on whether the +stream is to be added for recording SQL commands, results or +both." + (when (or (eq type :both) (eq type :commands)) + (unless (member stream + (list-sql-streams :type :commands :database database)) + (setf (command-recording-stream database) + (apply #'make-broadcast-stream + (cons stream (list-sql-streams :type :commands + :database database)))))) + (when (or (eq type :both) (eq type :results)) + (unless (member stream (list-sql-streams :type :results :database database)) + (setf (result-recording-stream database) + (apply #'make-broadcast-stream + (cons stream (list-sql-streams :type :results + :database database)))))) + stream) + +(defun delete-sql-stream (stream &key (type :commands) + (database *default-database*)) + "Removes the supplied stream STREAM from the recording broadcast +stream for the SQL recording type specified by TYPE on DATABASE +which defaults to *DEFAULT-DATABASE*. TYPE must be one +of :commands, :results, or :both, defaulting to :commands, +depending on whether the stream is to be added for recording SQL +commands, results or both." + (when (or (eq type :both) (eq type :commands)) + (setf (command-recording-stream database) + (apply #'make-broadcast-stream + (remove stream (list-sql-streams :type :commands + :database database))))) + (when (or (eq type :both) (eq type :results)) + (setf (result-recording-stream database) + (apply #'make-broadcast-stream + (remove stream (list-sql-streams :type :results + :database database))))) + stream) + +(defun list-sql-streams (&key (type :commands) (database *default-database*)) + "Returns the list of component streams for the broadcast stream +recording SQL commands sent to and/or results returned from +DATABASE which defaults to *DEFAULT-DATABASE*. TYPE must be one +of :commands, :results, or :both, defaulting to :commands, and +determines whether the listed streams contain those recording SQL +commands, results or both." + (let ((crs (command-recording-stream database)) + (rrs (result-recording-stream database))) + (cond + ((eq type :commands) + (when crs (broadcast-stream-streams crs))) + ((eq type :results) + (when rrs (broadcast-stream-streams rrs))) + ((eq type :both) + (append (when crs (broadcast-stream-streams crs)) + (when rrs (broadcast-stream-streams rrs)))) + (t + (error "Unknown recording type. ~A" type))))) + +(defun sql-stream (&key (type :commands) (database *default-database*)) + "Returns the broadcast stream used for recording SQL commands +sent to or results returned from DATABASE which defaults to +*DEFAULT-DATABASE*. TYPE must be one of :commands or :results, +defaulting to :commands, and determines whether the stream +returned is that used for recording SQL commands or results." + (cond + ((eq type :commands) + (command-recording-stream database)) + ((eq type :results) + (result-recording-stream database)) + (t + (error "Unknown recording type. ~A" type)))) + +(defun record-sql-command (expr database) + (when database + (with-slots (command-recording-stream) + database + (when command-recording-stream + (format command-recording-stream "~&;; ~A ~A => ~A~%" + (iso-timestring (get-time)) + (database-name database) + expr))))) + +(defun record-sql-result (res database) + (when database + (with-slots (result-recording-stream) + database + (when result-recording-stream + (format result-recording-stream "~&;; ~A ~A <= ~A~%" + (iso-timestring (get-time)) + (database-name database) + res))))) + + + diff --git a/sql/sequences.lisp b/sql/sequences.lisp new file mode 100644 index 0000000..1b32f29 --- /dev/null +++ b/sql/sequences.lisp @@ -0,0 +1,103 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Generic sequence implementation. Backends should use native sequences if +;;;; are available. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defclass generic-database (database) + () + (:documentation "Encapsulate same behavior across backends.")) + + +;;; Sequence functions + +(defvar *old-sequence-names* nil + "Should CLSQL use its old sequence naming scheme _CLSQL_SEQ_{table} instead + of the current scheme {table}_CLSQL_SEQ") + +(defun %sequence-name-to-table (sequence-name database) + (escaped + (combine-database-identifiers + (if *old-sequence-names* + (list '_CLSQL_SEQ sequence-name) + (list sequence-name 'CLSQL_SEQ)) + database))) + +(defmethod database-create-sequence (sequence-name database) + (let ((table-name (%sequence-name-to-table sequence-name database))) + (database-execute-command + (concatenate 'string "CREATE TABLE " table-name + " (last_value int NOT NULL PRIMARY KEY, increment_by int, min_value int, is_called char(1))") + database) + (database-execute-command + (concatenate 'string "INSERT INTO " table-name + " VALUES (1,1,1,'f')") + database))) + +(defmethod database-drop-sequence (sequence-name database) + (database-execute-command + (concatenate 'string "DROP TABLE " (%sequence-name-to-table sequence-name database)) + database)) + +(defun %seq-name-key () + (if *old-sequence-names* + "_CLSQL_SEQ_" + "_CLSQL_SEQ")) + +(defun %table-name-to-sequence-name (table-name) + ;; if this was escaped it still should be, + ;; if it wasnt it still shouldnt-be + (check-type table-name string) + (replace-all table-name (%seq-name-key) "")) + +(defmethod database-list-sequences (database &key (owner nil)) + (declare (ignore owner)) + (mapcan #'(lambda (s) + (and (search (%seq-name-key) s :test #'string-equal) + (list (%table-name-to-sequence-name s)))) + (database-list-tables-and-sequences database))) + +(defmethod database-set-sequence-position (sequence-name position database) + (database-execute-command + (format nil "UPDATE ~A SET last_value=~A,is_called='t'" + (%sequence-name-to-table sequence-name database) + position) + database) + position) + +(defmethod database-sequence-next (sequence-name database) + (without-interrupts + (let* ((table-name (%sequence-name-to-table sequence-name database)) + (tuple + (car (database-query + (concatenate 'string "SELECT last_value,is_called FROM " + table-name) + database :auto nil)))) + (cond + ((char-equal (schar (second tuple) 0) #\f) + (database-execute-command + (format nil "UPDATE ~A SET is_called='t'" table-name) + database) + (car tuple)) + (t + (let ((new-pos (1+ (car tuple)))) + (database-execute-command + (format nil "UPDATE ~A SET last_value=~D" table-name new-pos) + database) + new-pos)))))) + +(defmethod database-sequence-last (sequence-name database) + (without-interrupts + (caar (database-query + (concatenate 'string "SELECT last_value FROM " + (%sequence-name-to-table sequence-name database)) + database :auto nil)))) diff --git a/sql/syntax.lisp b/sql/syntax.lisp new file mode 100644 index 0000000..68fa8aa --- /dev/null +++ b/sql/syntax.lisp @@ -0,0 +1,198 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; CLSQL square bracket symbolic query syntax. Functions for +;;;; enabling and disabling the syntax and for building SQL +;;;; expressions using the syntax. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defvar *original-readtable* nil) + +(defvar *sql-macro-open-char* #\[) + +(defvar *sql-macro-close-char* #\]) + +(defvar *restore-sql-reader-syntax* nil) + + +;; Exported functions for disabling SQL syntax. + +(defmacro disable-sql-reader-syntax () + "Turns off the SQL reader syntax setting the syntax state such +that if the syntax is subsequently enabled, +RESTORE-SQL-READER-SYNTAX-STATE will disable it again." + '(eval-when (:compile-toplevel :load-toplevel :execute) + (setf *restore-sql-reader-syntax* nil) + (%disable-sql-reader-syntax))) + +(defmacro locally-disable-sql-reader-syntax () + "Turns off the SQL reader syntax without changing the syntax +state such that RESTORE-SQL-READER-SYNTAX-STATE will re-establish +the current syntax state." + '(eval-when (:compile-toplevel :load-toplevel :execute) + (%disable-sql-reader-syntax))) + +(defun %disable-sql-reader-syntax () + (when *original-readtable* + (setf *readtable* *original-readtable* + *original-readtable* nil)) + (values)) + + +;; Exported functions for enabling SQL syntax. + +(defmacro enable-sql-reader-syntax () + "Turns on the SQL reader syntax setting the syntax state such +that if the syntax is subsequently disabled, +RESTORE-SQL-READER-SYNTAX-STATE will enable it again." + '(eval-when (:compile-toplevel :load-toplevel :execute) + (setf *restore-sql-reader-syntax* t) + (%enable-sql-reader-syntax))) + +(defmacro locally-enable-sql-reader-syntax () + "Turns on the SQL reader syntax without changing the syntax +state such that RESTORE-SQL-READER-SYNTAX-STATE will re-establish +the current syntax state." + '(eval-when (:compile-toplevel :load-toplevel :execute) + (%enable-sql-reader-syntax))) + +(defmacro file-enable-sql-reader-syntax () + "Turns on the SQL reader syntax for the rest of the file. +The CL spec says that when finished loading a file the original +*readtable* is restored. clhs COMPILE-FILE" + '(eval-when (:compile-toplevel :load-toplevel :execute) + (setf *readtable* (copy-readtable)) + (set-macro-character *sql-macro-open-char* #'sql-reader-open) + (set-macro-character *sql-macro-close-char* (get-macro-character #\))))) + +(defun %enable-sql-reader-syntax () + (unless *original-readtable* + (setf *original-readtable* *readtable* + *readtable* (copy-readtable)) + (set-macro-character *sql-macro-open-char* #'sql-reader-open) + (set-macro-character *sql-macro-close-char* (get-macro-character #\)))) + (values)) + +(defmacro restore-sql-reader-syntax-state () + "Enables the SQL reader syntax if ENABLE-SQL-READER-SYNTAX has +been called more recently than DISABLE-SQL-READER-SYNTAX and +otherwise disables the SQL reader syntax. By default, the SQL +reader syntax is disabled." + '(eval-when (:compile-toplevel :load-toplevel :execute) + (if *restore-sql-reader-syntax* + (%enable-sql-reader-syntax) + (%disable-sql-reader-syntax)))) + +(defun sql-reader-open (stream char) + (declare (ignore char)) + (let ((sqllist (read-delimited-list #\] stream t))) + (unless *read-suppress* + (handler-case + (cond ((string= (write-to-string (car sqllist)) "||") + (cons (sql-operator 'concat-op) (cdr sqllist))) + ((and (= (length sqllist) 1) (eql (car sqllist) '*)) + (apply #'generate-sql-reference sqllist)) + ((sql-operator (car sqllist)) + (cons (sql-operator (car sqllist)) (cdr sqllist))) + (t (apply #'generate-sql-reference sqllist))) + (sql-user-error (c) + (error 'sql-user-error + :message (format nil "Error ~A occured while attempting to parse '~A' at file position ~A" + (sql-user-error-message c) sqllist (file-position stream)))))))) + +(defun generate-sql-reference (&rest arglist) + (cond ((= (length arglist) 1) ; string, table or attribute + (let ((arg (first arglist))) + (typecase arg + (string (sql-expression :string arg)) + (symbol ;; handle . separated names + (let* ((sn (symbol-name arg)) + (idx (position #\. sn))) + (cond + (idx (sql-expression :table (intern (subseq sn 0 idx)) + :attribute (intern (subseq sn (+ idx 1))) )) + (T (sql-expression :attribute arg)))) + )))) + ((<= 2 (length arglist)) + (let ((sqltype (when (keywordp (caddr arglist)) (caddr arglist) nil))) + (cond + ((stringp (cadr arglist)) + (sql-expression :table (car arglist) + :alias (cadr arglist) + :type sqltype)) + ((keywordp (cadr arglist)) + (sql-expression :attribute (car arglist) + :type (cadr arglist))) + (t + (sql-expression :attribute (cadr arglist) + :table (car arglist) + :type sqltype))))) + (t + (error 'sql-user-error :message "bad expression syntax")))) + + +;; Exported functions for dealing with SQL syntax + +(defun sql (&rest args) + "Returns an SQL string generated from the expressions ARGS. The +expressions are translated into SQL strings and then concatenated +with a single space delimiting each expression. An error of type +SQL-USER-ERROR is signalled if any element in ARGS is not of the +supported types (a symbol, string, number or symbolic SQL +expression) or a list or vector containing only these supported +types." + (format nil "~{~A~^ ~}" (mapcar #'sql-output args))) + +(defun sql-expression (&key string table alias attribute type) + "Returns an SQL expression constructed from the supplied +arguments which may be combined as follows: ATTRIBUTE and TYPE; +ATTRIBUTE; ALIAS or TABLE and ATTRIBUTE and TYPE; ALIAS or TABLE +and ATTRIBUTE; TABLE, ATTRIBUTE and TYPE; TABLE and ATTRIBUTE; +TABLE and ALIAS; TABLE; and STRING. An error of type +SQL-USER-ERROR is signalled if an unsupported combination of +keyword arguments is specified." + (cond + (string + (make-instance 'sql :string string)) + (attribute + (make-instance 'sql-ident-attribute :name attribute + :qualifier (or table alias) + :type type)) + ((and table (not attribute)) + (make-instance 'sql-ident-table :name table + :table-alias alias)))) + +(defun sql-operator (operator) + "Returns the Lisp symbol corresponding to the SQL operator + represented by the symbol OPERATOR. If OPERATOR does not + represent a supported SQL operator or is not a symbol, nil is + returned." + (typecase operator + (string nil) + (symbol (values (gethash (symbol-name-default-case (symbol-name operator)) + *sql-op-table*))))) + +(defun sql-operation (operator &rest args) + "Returns an SQL expression constructed from the supplied symbol +OPERATOR representing an SQL operator or function and its +arguments ARGS. An error of type SQL-USER-ERROR is signalled if +OPERATOR is not a symbol representing a supported SQL +operator. If OPERATOR is passed the symbol FUNCTION then the +first value in ARGS must be a string representing a valid SQL +function and the remaining values in ARGS its arguments as +strings." + (if (sql-operator operator) + (apply (symbol-function (sql-operator operator)) args) + (error 'sql-user-error + :message + (format nil "~A is not a recognized SQL operator." operator)))) + + diff --git a/sql/time.lisp b/sql/time.lisp new file mode 100644 index 0000000..0bb18c3 --- /dev/null +++ b/sql/time.lisp @@ -0,0 +1,1359 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; A variety of structures and function for creating and +;;;; manipulating dates, times, durations and intervals for +;;;; CLSQL. +;;;; +;;;; This file was originally part of ODCL and is Copyright (c) 2002 - +;;;; 2003 onShore Development, Inc. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +;; ------------------------------------------------------------ +;; Months + +(defvar *month-keywords* + '(:january :february :march :april :may :june :july :august :september + :october :november :december)) + +(defvar *month-names* + '("" "January" "February" "March" "April" "May" "June" "July" "August" + "September" "October" "November" "December")) + +(defun month-name (month-index) + (nth month-index *month-names*)) + +(defun ordinal-month (month-keyword) + "Return the zero-based month number for the given MONTH keyword." + (position month-keyword *month-keywords*)) + + +;; ------------------------------------------------------------ +;; Days + +(defvar *day-keywords* + '(:sunday :monday :tuesday :wednesday :thursday :friday :saturday)) + +(defvar *day-names* + '("Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")) + +(defun day-name (day-index) + (nth day-index *day-names*)) + +(defun ordinal-day (day-keyword) + "Return the zero-based day number for the given DAY keyword." + (position day-keyword *day-keywords*)) + + +;; ------------------------------------------------------------ +;; time classes: wall-time, duration + +(eval-when (:compile-toplevel :load-toplevel) + +(defstruct (wall-time (:conc-name time-) + (:constructor %make-wall-time) + (:print-function %print-wall-time)) + (mjd 0 :type fixnum) + (second 0 :type fixnum) + (usec 0 :type fixnum)) + +(defun %print-wall-time (time stream depth) + (declare (ignore depth)) + (if *print-escape* + (format stream "#" (format-time nil time)) + (format-time stream time :format :pretty))) + +(defstruct (duration (:constructor %make-duration) + (:print-function %print-duration)) + (year 0 :type fixnum) + (month 0 :type fixnum) + (day 0 :type fixnum) + (hour 0 :type fixnum) + (second 0 :type fixnum) + (minute 0 :type fixnum) + (usec 0 :type fixnum)) + +(defun %print-duration (duration stream depth) + (declare (ignore depth)) + (if *print-escape* + (format stream "#" + (format-duration nil duration :precision :second)) + (format-duration stream duration :precision :second))) + +(defstruct (date (:constructor %make-date) + (:print-function %print-date)) + (mjd 0 :type fixnum)) + +(defun %print-date (date stream depth) + (declare (ignore depth)) + (if *print-escape* + (format stream "#" (format-date nil date)) + (format-date stream date :format :pretty))) + +);eval-when + +(defun duration-timestring (duration) + (let ((second (duration-second duration)) + (minute (duration-minute duration)) + (hour (duration-hour duration)) + (day (duration-day duration)) + (month (duration-month duration)) + (year (duration-year duration))) + (format nil "P~dY~dM~dD~dH~dM~dS" year month day hour minute second))) + + +;; ------------------------------------------------------------ +;; Constructors + +(defun make-time (&key (year 0) (month 1) (day 1) (hour 0) (minute 0) + (second 0) (usec 0) (offset 0)) + (let ((mjd (gregorian-to-mjd month day year)) + (sec (+ (* hour 60 60) + (* minute 60) + second (- offset)))) + (multiple-value-bind (day-add raw-sec) + (floor sec (* 60 60 24)) + (%make-wall-time :mjd (+ mjd day-add) :second raw-sec :usec usec)))) + +(defun make-date (&key (year 0) (month 1) (day 1) (hour 0) (minute 0) + (second 0) (usec 0) (offset 0)) + (time->date (make-time :year year :month month :day day :hour hour + :minute minute :second second :usec usec :offset offset))) + +(defun copy-time (time) + (%make-wall-time :mjd (time-mjd time) + :second (time-second time))) + +(defun utime->time (utime) + "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)" + (multiple-value-bind (second minute hour day mon year) + (decode-universal-time utime) + (make-time :year year :month mon :day day :hour hour :minute minute + :second second))) + +(defun date->time (date) + "Returns a walltime for the given date" + (%make-wall-time :mjd (date-mjd date))) + +(defun time->date (time) + "Returns a date for the given wall time (obvious loss in resolution)" + (%make-date :mjd (time-mjd time))) + +(defun get-time () + "Return a pair: (GREGORIAN DAY . TIME-OF-DAY)" + (utime->time (get-universal-time))) + +(defun get-date () + "Returns a date for today" + (time->date (get-time))) + +(defun make-duration (&key (year 0) (month 0) (day 0) (hour 0) (minute 0) + (second 0) (usec 0)) + (multiple-value-bind (second-add usec-1000000) + (floor usec 1000000) + (multiple-value-bind (minute-add second-60) + (floor (+ second second-add) 60) + (multiple-value-bind (hour-add minute-60) + (floor (+ minute minute-add) 60) + (multiple-value-bind (day-add hour-24) + (floor (+ hour hour-add) 24) + (%make-duration :year year :month month :day (+ day day-add) + :hour hour-24 + :minute minute-60 + :second second-60 + :usec usec-1000000)))))) + + +;; ------------------------------------------------------------ +;; Accessors + +(defun time-hms (time) + (multiple-value-bind (hourminute second) + (floor (time-second time) 60) + (multiple-value-bind (hour minute) + (floor hourminute 60) + (values hour minute second)))) + +(defun time-ymd (time) + (destructuring-bind (month day year) + (mjd-to-gregorian (time-mjd time)) + (values year month day))) + +(defun time-dow (time) + "Return the 0 indexed Day of the week starting with Sunday" + (mod (+ 3 (time-mjd time)) 7)) + +(defun decode-time (time) + "returns the decoded time as multiple values: usec, second, minute, hour, + day, month, year, integer day-of-week" + (multiple-value-bind (year month day) + (time-ymd time) + (multiple-value-bind (hour minute second) + (time-hms time) + (values (time-usec time) second minute hour day month year (mod (+ (time-mjd time) 3) 7))))) + +(defun date-ymd (date) + (time-ymd (date->time date))) + +(defun date-dow (date) + (time-dow (date->time date))) + +(defun decode-date (date) + "returns the decoded date as multiple values: day month year integer day-of-week" + (multiple-value-bind (year month day) + (time-ymd (date->time date)) + (values day month year (date-dow date)))) + +;; duration specific +(defun duration-reduce (duration precision &optional round) + (ecase precision + (:usec + (+ (duration-usec duration) + (* (duration-reduce duration :second) 1000000))) + (:second + (+ (if round + (floor (duration-usec duration) 500000) + 0) + (duration-second duration) + (* (duration-reduce duration :minute) 60))) + (:minute + (+ (if round + (floor (duration-second duration) 30) + 0) + (duration-minute duration) + (* (duration-reduce duration :hour) 60))) + (:hour + (+ (if round + (floor (duration-minute duration) 30) + 0) + (duration-hour duration) + (* (duration-reduce duration :day) 24))) + (:day + (+ (if round + (floor (duration-hour duration) 12) + 0) + (duration-day duration))))) + + +;; ------------------------------------------------------------ +;; Arithemetic and comparators + +(defun duration= (duration-a duration-b) + (= (duration-reduce duration-a :usec) + (duration-reduce duration-b :usec))) + +(defun duration< (duration-a duration-b) + (< (duration-reduce duration-a :usec) + (duration-reduce duration-b :usec))) + +(defun duration<= (duration-a duration-b) + (<= (duration-reduce duration-a :usec) + (duration-reduce duration-b :usec))) + +(defun duration>= (x y) + (duration<= y x)) + +(defun duration> (x y) + (duration< y x)) + +(defun %time< (x y) + (let ((mjd-x (time-mjd x)) + (mjd-y (time-mjd y))) + (if (/= mjd-x mjd-y) + (< mjd-x mjd-y) + (if (/= (time-second x) (time-second y)) + (< (time-second x) (time-second y)) + (< (time-usec x) (time-usec y)))))) + +(defun %time>= (x y) + (if (/= (time-mjd x) (time-mjd y)) + (>= (time-mjd x) (time-mjd y)) + (if (/= (time-second x) (time-second y)) + (>= (time-second x) (time-second y)) + (>= (time-usec x) (time-usec y))))) + +(defun %time<= (x y) + (if (/= (time-mjd x) (time-mjd y)) + (<= (time-mjd x) (time-mjd y)) + (if (/= (time-second x) (time-second y)) + (<= (time-second x) (time-second y)) + (<= (time-usec x) (time-usec y))))) + +(defun %time> (x y) + (if (/= (time-mjd x) (time-mjd y)) + (> (time-mjd x) (time-mjd y)) + (if (/= (time-second x) (time-second y)) + (> (time-second x) (time-second y)) + (> (time-usec x) (time-usec y))))) + +(defun %time= (x y) + (and (= (time-mjd x) (time-mjd y)) + (= (time-second x) (time-second y)) + (= (time-usec x) (time-usec y)))) + +(defun time= (number &rest more-numbers) + "Returns T if all of its arguments are numerically equal, NIL otherwise." + (do ((nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (if (not (%time= (car nlist) number)) (return nil)))) + +(defun time/= (number &rest more-numbers) + "Returns T if no two of its arguments are numerically equal, NIL otherwise." + (do* ((head number (car nlist)) + (nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (unless (do* ((nl nlist (cdr nl))) + ((atom nl) t) + (declare (list nl)) + (if (%time= head (car nl)) (return nil))) + (return nil)))) + +(defun time< (number &rest more-numbers) + "Returns T if its arguments are in strictly increasing order, NIL otherwise." + (do* ((n number (car nlist)) + (nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (if (not (%time< n (car nlist))) (return nil)))) + +(defun time> (number &rest more-numbers) + "Returns T if its arguments are in strictly decreasing order, NIL otherwise." + (do* ((n number (car nlist)) + (nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (if (not (%time> n (car nlist))) (return nil)))) + +(defun time<= (number &rest more-numbers) + "Returns T if arguments are in strictly non-decreasing order, NIL otherwise." + (do* ((n number (car nlist)) + (nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (if (not (%time<= n (car nlist))) (return nil)))) + +(defun time>= (number &rest more-numbers) + "Returns T if arguments are in strictly non-increasing order, NIL otherwise." + (do* ((n number (car nlist)) + (nlist more-numbers (cdr nlist))) + ((atom nlist) t) + (declare (list nlist)) + (if (not (%time>= n (car nlist))) (return nil)))) + +(defun time-max (number &rest more-numbers) + "Returns the greatest of its arguments." + (do ((nlist more-numbers (cdr nlist)) + (result number)) + ((null nlist) (return result)) + (declare (list nlist)) + (if (%time> (car nlist) result) (setf result (car nlist))))) + +(defun time-min (number &rest more-numbers) + "Returns the least of its arguments." + (do ((nlist more-numbers (cdr nlist)) + (result number)) + ((null nlist) (return result)) + (declare (list nlist)) + (if (%time< (car nlist) result) (setf result (car nlist))))) + +(defun time-compare (time-a time-b) + (let ((mjd-a (time-mjd time-a)) + (mjd-b (time-mjd time-b)) + (sec-a (time-second time-a)) + (sec-b (time-second time-b)) + (usec-a (time-usec time-a)) + (usec-b (time-usec time-b))) + (if (= mjd-a mjd-b) + (if (= sec-a sec-b) + (if (= usec-a usec-b) + :equal + (if (< usec-a usec-b) + :less-than + :greater-than)) + (if (< sec-a sec-b) + :less-than + :greater-than)) + (if (< mjd-a mjd-b) + :less-than + :greater-than)))) + +; now the same for dates +(eval-when (:compile-toplevel :load-toplevel) +(defun replace-string (string1 search-string replace-string &key (test #'string=)) + "Search within string1 for search-string, replace with replace-string, non-destructively." + (let ((replace-string-length (length replace-string)) + (search-string-length (length search-string))) + (labels ((sub-replace-string (current-string position) + (let ((found-position (search search-string current-string :test test :start2 position))) + (if (null found-position) + current-string + (sub-replace-string (concatenate 'string + (subseq current-string 0 found-position) + replace-string + (subseq current-string (+ found-position search-string-length))) + (+ position replace-string-length)))))) + (sub-replace-string string1 0)))) +);eval-when + +(defmacro wrap-time-for-date (time-func &key (result-func)) + (let ((date-func (intern (replace-string (symbol-name time-func) + (symbol-name-default-case "TIME") + (symbol-name-default-case "DATE"))))) + `(defun ,date-func (number &rest more-numbers) + (let ((result (apply #',time-func (mapcar #'date->time (cons number more-numbers))))) + ,(if result-func + `(funcall #',result-func result) + 'result))))) + +(wrap-time-for-date time=) +(wrap-time-for-date time/=) +(wrap-time-for-date time<) +(wrap-time-for-date time>) +(wrap-time-for-date time<=) +(wrap-time-for-date time>=) +(wrap-time-for-date time-max :result-func time->date) +(wrap-time-for-date time-min :result-func time->date) + +(defun date-compare (date-a date-b) + (time-compare (date->time date-a) (date->time date-b))) + +;; ------------------------------------------------------------ +;; Formatting and output +(defun db-timestring (time &key stream) + "return the string to store the given time in the database" + (if stream + (progn (write-char #\' stream) (iso-timestring time :stream stream) (write-char #\' stream)) + (concatenate 'string "'" (iso-timestring time) "'"))) + +(defun iso-timestring (time &key stream) + (multiple-value-bind (usec sec min hour day month year dow) + (decode-time time) + (declare (ignore dow)) + (flet ((fmt (stream) + (when (< year 1000) (princ #\0 stream)) + (when (< year 100) (princ #\0 stream)) + (when (< year 10) (princ #\0 stream)) + (princ year stream) + (princ #\- stream) + (when (< month 10) (princ #\0 stream)) + (princ month stream) + (princ #\- stream) + (when (< day 10) (princ #\0 stream)) + (princ day stream) + (princ #\T stream) ;strict ISO says T here isn't optional. + (when (< hour 10) (princ #\0 stream)) + (princ hour stream) + (princ #\: stream) + (when (< min 10) (princ #\0 stream)) + (princ min stream) + (princ #\: stream) + (when (< sec 10) (princ #\0 stream)) + (princ sec stream) + (when (and usec (plusp usec)) + ;; we dont do this because different dbs support differnt precision levels + (princ #\. stream) + (loop for i from 5 downto 0 + for x10 = (expt 10 i) + do (multiple-value-bind (quo rem) + (floor (/ usec x10)) + (setf usec (- usec (* quo x10))) + (princ quo stream) + (when (= rem 0) (return))))) + nil)) + (if stream + (fmt stream) + (with-output-to-string (stream) + (fmt stream)))))) + +(defun db-datestring (date) + (db-timestring (date->time date))) +(defun iso-datestring (date) + (iso-timestring (date->time date))) + + +;; ------------------------------------------------------------ +;; Intervals + +(defstruct interval + (start nil) + (end nil) + (name nil) + (contained nil) + (type nil) + (data nil)) + +;; fix : should also return :contains / :contained + +(defun interval-relation (x y) + "Compare the relationship of node x to node y. Returns either +:contained :contains :follows :overlaps or :precedes." + (let ((xst (interval-start x)) + (xend (interval-end x)) + (yst (interval-start y)) + (yend (interval-end y))) + (case (time-compare xst yst) + (:equal + (case (time-compare xend yend) + (:less-than + :contained) + ((:equal :greater-than) + :contains))) + (:greater-than + (case (time-compare xst yend) + ((:equal :greater-than) + :follows) + (:less-than + (case (time-compare xend yend) + ((:less-than :equal) + :contained) + ((:greater-than) + :overlaps))))) + (:less-than + (case (time-compare xend yst) + ((:equal :less-than) + :precedes) + (:greater-than + (case (time-compare xend yend) + (:less-than + :overlaps) + ((:equal :greater-than) + :contains)))))))) + +;; ------------------------------------------------------------ +;; interval lists + +(defun sort-interval-list (list) + (sort list (lambda (x y) + (case (interval-relation x y) + ((:precedes :contains) t) + ((:follows :overlaps :contained) nil))))) + +;; interval push will return its list of intervals in strict order. +(defun interval-push (interval-list interval &optional container-rule) + (declare (ignore container-rule)) + (let ((sorted-list (sort-interval-list interval-list))) + (dotimes (x (length sorted-list)) + (let ((elt (nth x sorted-list))) + (case (interval-relation elt interval) + (:follows + (return-from interval-push (insert-at-index x sorted-list interval))) + (:contains + (return-from interval-push + (replace-at-index x sorted-list + (make-interval :start (interval-start elt) + :end (interval-end elt) + :type (interval-type elt) + :contained (interval-push (interval-contained elt) interval) + :data (interval-data elt))))) + ((:overlaps :contained) + (error "Overlap"))))) + (append sorted-list (list interval)))) + +;; interval lists + +(defun interval-match (list time) + "Return the index of the first interval in list containing time" + ;; this depends on ordering of intervals! + (let ((list (sort-interval-list list))) + (dotimes (x (length list)) + (let ((elt (nth x list))) + (when (and (time<= (interval-start elt) time) + (time< time (interval-end elt))) + (return-from interval-match x)))))) + +(defun interval-clear (list time) + (dotimes (x (length list)) + (let ((elt (nth x list))) + (when (and (time<= (interval-start elt) time) + (time< time (interval-end elt))) + (if (interval-match (interval-contained elt) time) + (return-from interval-clear + (replace-at-index x list + (make-interval :start (interval-start elt) + :end (interval-end elt) + :type (interval-type elt) + :contained (interval-clear (interval-contained elt) time) + :data (interval-data elt)))) + (return-from interval-clear + (delete-at-index x list))))))) + +(defun interval-edit (list time start end &optional tag) + "Attempts to modify the most deeply nested interval in list which +begins at time. If no changes are made, returns nil." + ;; function required sorted interval list + (let ((list (sort-interval-list list))) + (if (null list) nil + (dotimes (x (length list)) + (let ((elt (nth x list))) + (when (and (time<= (interval-start elt) time) + (time< time (interval-end elt))) + (or (interval-edit (interval-contained elt) time start end tag) + (cond ((and (< 0 x) + (time< start (interval-end (nth (1- x) list)))) + (error "Overlap of previous interval")) + ((and (< x (1- (length list))) + (time< (interval-start (nth (1+ x) list)) end)) + (error "~S ~S ~S ~S Overlap of next interval" x (length list) (interval-start (nth (1+ x) list)) end )) + ((time= (interval-start elt) time) + (return-from interval-edit + (replace-at-index x list + (make-interval :start start + :end end + :type (interval-type elt) + :contained (restrict-intervals (interval-contained elt) start end) + :data (or tag (interval-data elt)))))))))))))) + +(defun restrict-intervals (list start end &aux newlist) + (let ((test-interval (make-interval :start start :end end))) + (dolist (elt list) + (when (equal :contained + (interval-relation elt test-interval)) + (push elt newlist))) + (nreverse newlist))) + +;;; utils from odcl/list.lisp + +(defun replace-at-index (idx list elt) + (cond ((= idx 0) + (cons elt (cdr list))) + ((= idx (1- (length list))) + (append (butlast list) (list elt))) + (t + (append (subseq list 0 idx) + (list elt) + (subseq list (1+ idx)))))) + +(defun insert-at-index (idx list elt) + (cond ((= idx 0) + (cons elt list)) + ((= idx (1- (length list))) + (append list (list elt))) + (t + (append (subseq list 0 idx) + (list elt) + (subseq list idx))))) + +(defun delete-at-index (idx list) + (cond ((= idx 0) + (cdr list)) + ((= idx (1- (length list))) + (butlast list)) + (t + (append (subseq list 0 idx) + (subseq list (1+ idx)))))) + + +;; ------------------------------------------------------------ +;; return MJD for Gregorian date + +(defun gregorian-to-mjd (month day year) + (let ((b 0) + (month-adj month) + (year-adj (if (< year 0) + (+ year 1) + year)) + d + c) + (when (< month 3) + (incf month-adj 12) + (decf year-adj)) + (unless (or (< year 1582) + (and (= year 1582) + (or (< month 10) + (and (= month 10) + (< day 15))))) + (let ((a (floor (/ year-adj 100)))) + (setf b (+ (- 2 a) (floor (/ a 4)))))) + (if (< year-adj 0) + (setf c (floor (- (* 365.25d0 year-adj) 679006.75d0))) + (setf c (floor (- (* 365.25d0 year-adj) 679006d0)))) + (setf d (floor (* 30.6001 (+ 1 month-adj)))) + ;; (cmsg "b ~s c ~s d ~s day ~s" b c d day) + (+ b c d day))) + +;; convert MJD to Gregorian date + +(defun mjd-to-gregorian (mjd) + (let (z r g a b c year month day) + (setf z (floor (+ mjd 678882))) + (setf r (- (+ mjd 678882) z)) + (setf g (- z .25)) + (setf a (floor (/ g 36524.25))) + (setf b (- a (floor (/ a 4)))) + (setf year (floor (/ (+ b g) 365.25))) + (setf c (- (+ b z) (floor (* 365.25 year)))) + (setf month (truncate (/ (+ (* 5 c) 456) 153))) + (setf day (+ (- c (truncate (/ (- (* 153 month) 457) 5))) r)) + (when (> month 12) + (incf year) + (decf month 12)) + (list month day year))) + +(defun duration+ (time &rest durations) + "Add each DURATION to TIME, returning a new wall-time value." + (let ((year (duration-year time)) + (month (duration-month time)) + (day (duration-day time)) + (hour (duration-hour time)) + (minute (duration-minute time)) + (second (duration-second time)) + (usec (duration-usec time))) + (dolist (duration durations) + (incf year (duration-year duration)) + (incf month (duration-month duration)) + (incf day (duration-day duration)) + (incf hour (duration-hour duration)) + (incf minute (duration-minute duration)) + (incf second (duration-second duration)) + (incf usec (duration-usec duration))) + (make-duration :year year :month month :day day :hour hour :minute minute + :second second :usec usec))) + +(defun duration- (duration &rest durations) + "Subtract each DURATION from TIME, returning a new duration value." + (let ((year (duration-year duration)) + (month (duration-month duration)) + (day (duration-day duration)) + (hour (duration-hour duration)) + (minute (duration-minute duration)) + (second (duration-second duration)) + (usec (duration-usec duration))) + (dolist (duration durations) + (decf year (duration-year duration)) + (decf month (duration-month duration)) + (decf day (duration-day duration)) + (decf hour (duration-hour duration)) + (decf minute (duration-minute duration)) + (decf second (duration-second duration)) + (decf usec (duration-usec duration))) + (make-duration :year year :month month :day day :hour hour :minute minute + :second second :usec usec))) + +;; Date + Duration + +(defun time+ (time &rest durations) + "Add each DURATION to TIME, returning a new wall-time value." + (let ((new-time (copy-time time))) + (dolist (duration durations) + (roll new-time + :year (duration-year duration) + :month (duration-month duration) + :day (duration-day duration) + :hour (duration-hour duration) + :minute (duration-minute duration) + :second (duration-second duration) + :usec (duration-usec duration) + :destructive t)) + new-time)) + +(defun date+ (date &rest durations) + "Add each DURATION to DATE, returning a new date value. +Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing +it as separate calculations will not, as the time is chopped to a date before being returned." + (time->date (apply #'time+ (cons (date->time date) durations)))) + +(defun time- (time &rest durations) + "Subtract each DURATION from TIME, returning a new wall-time value." + (let ((new-time (copy-time time))) + (dolist (duration durations) + (roll new-time + :year (- (duration-year duration)) + :month (- (duration-month duration)) + :day (- (duration-day duration)) + :hour (- (duration-hour duration)) + :minute (- (duration-minute duration)) + :second (- (duration-second duration)) + :usec (- (duration-usec duration)) + :destructive t)) + new-time)) + +(defun date- (date &rest durations) + "Subtract each DURATION to DATE, returning a new date value. +Note that (barring daylight saving time) 12h + 12h will result in a new day, but doing +it as separate calculations will not, as the time is chopped to a date before being returned." + (time->date (apply #'time- (cons (date->time date) durations)))) + +(defun time-difference (time1 time2) + "Returns a DURATION representing the difference between TIME1 and +TIME2." + (flet ((do-diff (time1 time2) + + (let (day-diff sec-diff) + (setf day-diff (- (time-mjd time2) + (time-mjd time1))) + (if (> day-diff 0) + (progn (decf day-diff) + (setf sec-diff (+ (time-second time2) + (- (* 60 60 24) + (time-second time1))))) + (setf sec-diff (- (time-second time2) + (time-second time1)))) + (make-duration :day day-diff + :second sec-diff)))) + (if (time< time1 time2) + (do-diff time1 time2) + (do-diff time2 time1)))) + +(defun date-difference (date1 date2) + "Returns a DURATION representing the difference between TIME1 and +TIME2." + (time-difference (date->time date1) (date->time date2))) + +(defun format-date (stream date &key format + (date-separator "-") + (internal-separator " ")) + "produces on stream the datestring corresponding to the date +with the given options" + (format-time stream (date->time date) + :format format + :date-separator date-separator + :internal-separator internal-separator)) + +(defun format-time (stream time &key format + (date-separator "-") + (time-separator ":") + (internal-separator " ")) + "produces on stream the timestring corresponding to the wall-time +with the given options" + (let ((*print-circle* nil)) + (multiple-value-bind (usec second minute hour day month year dow) + (decode-time time) + (case format + (:pretty + (format stream "~A ~A, ~A ~D, ~D" + (pretty-time hour minute) + (day-name dow) + (month-name month) + day + year)) + (:short-pretty + (format stream "~A, ~D/~D/~D" + (pretty-time hour minute) + month day year)) + ((:iso :iso8601) (iso-timestring time :stream stream)) + (t (format stream "~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D~A~2,'0D.~6,'0D" + year date-separator month date-separator day + internal-separator hour time-separator minute time-separator + second usec) + ))))) + +(defun pretty-time (hour minute) + (cond + ((eq hour 0) + (format nil "12:~2,'0D AM" minute)) + ((eq hour 12) + (format nil "12:~2,'0D PM" minute)) + ((< hour 12) + (format nil "~D:~2,'0D AM" hour minute)) + ((and (> hour 12) (< hour 24)) + (format nil "~D:~2,'0D PM" (- hour 12) minute)) + (t + (error "pretty-time got bad hour")))) + +(defun leap-days-in-days (days) + ;; return the number of leap days between Mar 1 2000 and + ;; (Mar 1 2000) + days, where days can be negative + (if (< days 0) + (ceiling (/ (- days) (* 365 4))) + (floor (/ days (* 365 4))))) + +(defun current-year () + (third (mjd-to-gregorian (time-mjd (get-time))))) + +(defun current-month () + (first (mjd-to-gregorian (time-mjd (get-time))))) + +(defun current-day () + (second (mjd-to-gregorian (time-mjd (get-time))))) + +(defun parse-date-time (string) + "parses date like 08/08/01, 8.8.2001, eg" + (when (> (length string) 1) + (let ((m (current-month)) + (d (current-day)) + (y (current-year))) + (let ((integers (mapcar #'parse-integer (hork-integers string)))) + (case (length integers) + (1 + (setf y (car integers))) + (2 + (setf m (car integers)) + (setf y (cadr integers))) + (3 + (setf m (car integers)) + (setf d (cadr integers)) + (setf y (caddr integers))) + (t + (return-from parse-date-time)))) + (when (< y 100) + (incf y 2000)) + (make-time :year y :month m :day d)))) + +(defun hork-integers (input) + (let ((output '()) + (start 0)) + (dotimes (x (length input)) + (unless (<= 48 (char-code (aref input x)) 57) + (push (subseq input start x) output) + (setf start (1+ x)))) + (nreverse (push (subseq input start) output)))) + +(defun merged-time (day time-of-day) + (%make-wall-time :mjd (time-mjd day) + :second (time-second time-of-day))) + +(defun time-meridian (hours) + (cond ((= hours 0) + (values 12 "AM")) + ((= hours 12) + (values 12 "PM")) + ((< 12 hours) + (values (- hours 12) "PM")) + (t + (values hours "AM")))) + +(defgeneric to-string (val &rest keys) + ) + +(defmethod to-string ((time wall-time) &rest keys) + (destructuring-bind (&key (style :daytime) &allow-other-keys) + keys + (print-date time style))) + +(defun print-date (time &optional (style :daytime)) + (multiple-value-bind (usec second minute hour day month year dow) + (decode-time time) + (declare (ignore usec second)) + (multiple-value-bind (hours meridian) + (time-meridian hour) + (ecase style + (:time-of-day + ;; 2:00 PM + (format nil "~d:~2,'0d ~a" hours minute meridian)) + (:long-day + ;; October 11th, 2000 + (format nil "~a ~d, ~d" (month-name month) day year)) + (:month + ;; October + (month-name month)) + (:month-year + ;; October 2000 + (format nil "~a ~d" (month-name month) year)) + (:full + ;; 11:08 AM, November 22, 2002 + (format nil "~d:~2,'0d ~a, ~a ~d, ~d" + hours minute meridian (month-name month) day year)) + (:full+weekday + ;; 11:09 AM Friday, November 22, 2002 + (format nil "~d:~2,'0d ~a ~a, ~a ~d, ~d" + hours minute meridian (nth dow *day-names*) + (month-name month) day year)) + (:daytime + ;; 11:09 AM, 11/22/2002 + (format-time nil time :format :short-pretty)) + (:day + ;; 11/22/2002 + (format nil "~d/~d/~d" month day year)))))) + +(defun time-element (time element) + (multiple-value-bind (usec second minute hour day month year dow) + (decode-time time) + (declare (ignore usec)) + (ecase element + (:seconds + second) + (:minutes + minute) + (:hours + hour) + (:day-of-month + day) + (:integer-day-of-week + dow) + (:day-of-week + (nth dow *day-keywords*)) + (:month + month) + (:year + year)))) + +(defun date-element (date element) + (time-element (date->time date) element)) + +(defun format-duration (stream duration &key (precision :minute)) + (let ((second (duration-second duration)) + (minute (duration-minute duration)) + (hour (duration-hour duration)) + (day (duration-day duration)) + (month (duration-month duration)) + (year (duration-year duration)) + (return (null stream)) + (stream (or stream (make-string-output-stream)))) + (ecase precision + (:day + (setf hour 0 second 0 minute 0)) + (:hour + (setf second 0 minute 0)) + (:minute + (setf second 0)) + (:second + t)) + (if (= 0 year month day hour minute) + (format stream "0 minutes") + (let ((sent? nil)) + (when (< 0 year) + (format stream "~d year~p" year year) + (setf sent? t)) + (when (< 0 month) + (when sent? + (write-char #\Space stream)) + (format stream "~d month~p" month month) + (setf sent? t)) + (when (< 0 day) + (when sent? + (write-char #\Space stream)) + (format stream "~d day~p" day day) + (setf sent? t)) + (when (< 0 hour) + (when sent? + (write-char #\Space stream)) + (format stream "~d hour~p" hour hour) + (setf sent? t)) + (when (< 0 minute) + (when sent? + (write-char #\Space stream)) + (format stream "~d min~p" minute minute) + (setf sent? t)) + (when (< 0 second) + (when sent? + (write-char #\Space stream)) + (format stream "~d sec~p" second second)))) + (when return + (get-output-stream-string stream)))) + +(defgeneric midnight (self)) +(defmethod midnight ((self wall-time)) + "truncate hours, minutes and seconds" + (%make-wall-time :mjd (time-mjd self))) + +(defun roll (date &key (year 0) (month 0) (day 0) (second 0) (hour 0) + (minute 0) (usec 0) (destructive nil)) + (unless (= 0 year month) + (multiple-value-bind (year-orig month-orig day-orig) + (time-ymd date) + (multiple-value-bind (new-year new-month) + (floor (+ month month-orig (* 12 (+ year year-orig))) 12) + (let ((new-date (make-time :year new-year + :month new-month + :day day-orig + :second (time-second date) + :usec usec))) + (if destructive + (setf (time-mjd date) (time-mjd new-date)) + (setq date new-date)))))) + (let ((mjd (time-mjd date)) + (sec (time-second date))) + (multiple-value-bind (sec-new usec-new) + (floor (+ usec (time-usec date) + (* 1000000 + (+ sec second + (* 60 minute) + (* 60 60 hour)))) + 1000000) + (multiple-value-bind (mjd-new sec-new) + (floor sec-new (* 60 60 24)) + (if destructive + (progn + (setf (time-mjd date) (+ mjd mjd-new day) + (time-second date) sec-new + (time-usec date) usec-new) + date) + (%make-wall-time :mjd (+ mjd mjd-new day) + :second sec-new + :usec usec-new)))))) + +(defun roll-to (date size position) + (ecase size + (:month + (ecase position + (:beginning + (roll date :day (+ 1 + (- (time-element date :day-of-month))))) + (:end + (roll date :day (+ (days-in-month (time-element date :month) + (time-element date :year)) + (- (time-element date :day-of-month))))))))) + +(defun week-containing (time) + (let* ((midn (midnight time)) + (dow (time-element midn :integer-day-of-week))) + (list (roll midn :day (- dow)) + (roll midn :day (- 7 dow))))) + +(defun leap-year? (year) + "t if YEAR is a leap yeap in the Gregorian calendar" + (and (= 0 (mod year 4)) + (or (not (= 0 (mod year 100))) + (= 0 (mod year 400))))) + +(defun valid-month-p (month) + "t if MONTH exists in the Gregorian calendar" + (<= 1 month 12)) + +(defun valid-gregorian-date-p (date) + "t if DATE (year month day) exists in the Gregorian calendar" + (let ((max-day (days-in-month (nth 1 date) (nth 0 date)))) + (<= 1 (nth 2 date) max-day))) + +(defun days-in-month (month year &key (careful t)) + "the number of days in MONTH of YEAR, observing Gregorian leap year +rules" + (declare (type fixnum month year)) + (when careful + (check-type month (satisfies valid-month-p) + "between 1 (January) and 12 (December)")) + (if (eql month 2) ; feb + (if (leap-year? year) + 29 28) + (let ((even (mod (1- month) 2))) + (if (< month 8) ; aug + (- 31 even) + (+ 30 even))))) + +(defun day-of-year (year month day &key (careful t)) + "the day number within the year of the date DATE. For example, +1987 1 1 returns 1" + (declare (type fixnum year month day)) + (when careful + (let ((date (list year month day))) + (check-type date (satisfies valid-gregorian-date-p) + "a valid Gregorian date"))) + (let ((doy (+ day (* 31 (1- month))))) + (declare (type fixnum doy)) + (when (< 2 month) + (setf doy (- doy (floor (+ 23 (* 4 month)) 10))) + (when (leap-year? year) + (incf doy))) + doy)) + +(defun parse-yearstring (string) + (let ((year (or (parse-integer-insensitively string) + (extract-roman string)))) + (when (and year (< 1500 year 2500)) + (make-time :year year)))) + +(defun parse-integer-insensitively (string) + (let ((start (position-if #'digit-char-p string)) + (end (position-if #'digit-char-p string :from-end t))) + (when (and start end) + (parse-integer (subseq string start (1+ end)) :junk-allowed t)))) + +(defvar *roman-digits* + '((#\M . 1000) + (#\D . 500) + (#\C . 100) + (#\L . 50) + (#\X . 10) + (#\V . 5) + (#\I . 1))) + +(defun extract-roman (string &aux parse) + (dotimes (x (length string)) + (let ((val (cdr (assoc (aref string x) *roman-digits*)))) + (when (and val parse (< (car parse) val)) + (push (- (pop parse)) parse)) + (push val parse))) + (apply #'+ parse)) + + +;; ------------------------------------------------------------ +;; Parsing iso-8601 timestrings + +(define-condition iso-8601-syntax-error (sql-user-error) + ((bad-component;; year, month whatever + :initarg :bad-component + :reader bad-component)) + (:report (lambda (c stream) + (format stream "Bad component: ~A " (bad-component c))))) + +(defun parse-timestring (timestring &key (start 0) end junk-allowed) + "parse a timestring and return the corresponding wall-time. If the +timestring starts with P, read a duration; otherwise read an ISO 8601 +formatted date string." + (declare (ignore junk-allowed)) + (etypecase timestring + (wall-time timestring) + (date (date->time timestring)) + (string + (let ((string (subseq timestring start end))) + (if (char= (aref string 0) #\P) + (parse-iso-8601-duration string) + (parse-iso-8601-time string)))))) + +(defun parse-datestring (datestring &key (start 0) end junk-allowed) + "parse a ISO 8601 timestring and return the corresponding date. +Will throw a hissy fit if the date string is a duration. Will ignore any precision beyond day (hour/min/sec/usec)." + (etypecase datestring + (date datestring) + (wall-time (time->date datestring)) + (string + (let ((parsed-value + (parse-timestring + datestring :start start :end end :junk-allowed junk-allowed))) + (etypecase parsed-value + (date parsed-value) + (wall-time (time->date parsed-value))))))) + + +(defvar *iso-8601-duration-delimiters* + '((#\Y . :years) + (#\D . :days) + (#\H . :hours) + (#\M . :months/minutes) + (#\S . :seconds))) + +(defun iso-8601-delimiter (elt) + (cdr (assoc elt *iso-8601-duration-delimiters*))) + +(defun iso-8601-duration-subseq (string end) + (let* ((pos (position-if #'iso-8601-delimiter string :end end :from-end t)) + (pos2 (when pos + (position-if-not #'digit-char-p string :end pos :from-end t))) + (number (when pos2 + (parse-integer + (subseq string (1+ pos2) pos) :junk-allowed t)))) + (when number + (values number + (1+ pos) + (1+ pos2) + (iso-8601-delimiter (aref string pos)))))) + +(defun parse-iso-8601-duration (string) + "return a wall-time from a duration string" + (block parse + (let ((years 0) + (months 0) + (days 0) + (secs 0) + (hours 0) + (minutes 0) + (index (length string)) + (months/minutes nil)) + (loop + (multiple-value-bind (duration end next-index duration-type) + (iso-8601-duration-subseq string index) + (declare (ignore end)) + (case duration-type + (:years + (incf years duration)) + (:months/minutes + (if months/minutes + (incf months duration) + (progn + (setq months/minutes t) + (incf minutes duration)))) + (:days + (setq months/minutes t) + (incf days duration)) + (:hours + (setq months/minutes t) + (incf hours duration)) + (:seconds + (incf secs duration)) + (t + (return-from parse + (make-duration + :year years :month months :day days :hour hours + :minute minutes :second secs)))) + (setf index next-index)))))) + +;; e.g. 2000-11-11 00:00:00-06 + +(defun parse-iso-8601-time (string) + "return the wall-time corresponding to the given ISO 8601 datestring" + (multiple-value-bind (year month day hour minute second usec offset) + (syntax-parse-iso-8601 string) + (make-time :year year + :month month + :day day + :hour hour + :minute minute + :second second + :usec usec + :offset offset))) + + +(defun syntax-parse-iso-8601 (string) + ;; use strlen to determine if fractional seconds are present in timestamp + (let ((strlen (length string)) + year month day hour minute second usec gmt-sec-offset) + (handler-case + (progn + (setf year (parse-integer string :start 0 :end 4) + month (parse-integer string :start 5 :end 7) + day (parse-integer string :start 8 :end 10) + hour (if (<= 13 strlen) + (parse-integer string :start 11 :end 13) + 0) + minute (if (<= 16 strlen) + (parse-integer string :start 14 :end 16) + 0) + second (if (<= 19 strlen) + (parse-integer string :start 17 :end 19) + 0)) + (cond + ((and (> strlen 19) + (or (char= #\, (char string 19)) + (char= #\. (char string 19)))) + (multiple-value-bind (parsed-usec usec-end) + (parse-integer string :start 20 :junk-allowed t) + (let ((parsed-usec (and parsed-usec + (floor (* parsed-usec (expt 10 (+ 6 (- usec-end) 20))))))) + (setf usec (or parsed-usec 0) + gmt-sec-offset (if (<= (+ 3 usec-end) strlen) + (let ((skip-to (or (position #\+ string :start 19) + (position #\- string :start 19)))) + (if skip-to + (* 60 60 + (parse-integer string :start skip-to + :end (+ skip-to 3))) + 0)) + 0))))) + (t + (setf usec 0 + gmt-sec-offset (if (<= 22 strlen) + (let ((skip-to (or (position #\+ string :start 19) + (position #\- string :start 19)))) + (if skip-to + (* 60 60 + (parse-integer string :start skip-to + :end (+ skip-to 3))) + 0)) + 0)))) + (unless (< 0 year) + (error 'iso-8601-syntax-error + :bad-component '(year . 0))) + (unless (< 0 month) + (error 'iso-8601-syntax-error + :bad-component '(month . 0))) + (unless (< 0 day) + (error 'iso-8601-syntax-error + :bad-component '(month . 0))) + (values year month day hour minute second usec gmt-sec-offset)) + (simple-error () + (error 'iso-8601-syntax-error + :bad-component + (car (find-if (lambda (pair) (null (cdr pair))) + `((year . ,year) (month . ,month) + (day . ,day) (hour . ,hour) + (minute . ,minute) (second . ,second) + (usec . ,usec) + (timezone . ,gmt-sec-offset))))))))) diff --git a/sql/transaction.lisp b/sql/transaction.lisp new file mode 100644 index 0000000..24465d1 --- /dev/null +++ b/sql/transaction.lisp @@ -0,0 +1,152 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; +;;;; Transaction support +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defclass transaction () + ((commit-hooks :initform () :accessor commit-hooks) + (rollback-hooks :initform () :accessor rollback-hooks) + (previous-autocommit :initarg :previous-autocommit + :reader previous-autocommit) + (status :initform nil :accessor transaction-status + :documentation "nil or :committed"))) + +(defun add-transaction-commit-hook (commit-hook &key + (database *default-database*)) + "Adds COMMIT-HOOK, which should a designator for a function +with no required arguments, to the list of hooks run when COMMIT +is called on DATABASE which defaults to *DEFAULT-DATABASE*." + (when (transaction database) + (push commit-hook (commit-hooks (transaction database))))) + +(defun add-transaction-rollback-hook (rollback-hook + &key (database *default-database*)) + "Adds ROLLBACK-HOOK, which should a designator for a function +with no required arguments, to the list of hooks run when ROLLBACK +is called on DATABASE which defaults to *DEFAULT-DATABASE*." + (when (transaction database) + (push rollback-hook (rollback-hooks (transaction database))))) + +(defmethod database-start-transaction ((database database)) + (unless (transaction database) + (setf (transaction database) + (make-instance 'transaction :previous-autocommit + (database-autocommit database)))) + ;; TODO: database-autocommit might get lost in some scenarios + ;; when pooling connections + (setf (database-autocommit database) nil) + (when (= (incf (transaction-level database)) 1) + (let ((transaction (transaction database))) + (setf (commit-hooks transaction) nil + (rollback-hooks transaction) nil + (transaction-status transaction) nil) + (case (database-underlying-type database) + (:oracle nil) + (:mssql (execute-command "BEGIN TRANSACTION" :database database)) + (t (execute-command "BEGIN" :database database)))))) + +;;ODBC should potentially be using the following scheme for transactions: +;; turn off autocommit for begin. then use sqlendtran (or maybe sqltransact) +;; whatever is appropriate for this version of odbc. +(defmethod database-commit-transaction ((database database)) + (with-slots (transaction transaction-level autocommit) database + (if (plusp transaction-level) + (if (zerop (decf transaction-level)) + (progn + (case (database-underlying-type database) + (:mssql (execute-command "COMMIT TRANSACTION" :database database)) + (t (execute-command "COMMIT" :database database))) + (setf autocommit (previous-autocommit transaction)) + (map nil #'funcall (commit-hooks transaction))) + (setf (transaction-status (transaction database)) nil)) + (warn 'sql-warning + :message + (format nil "Cannot commit transaction against ~A because there is no transaction in progress." + database))))) + +(defmethod database-abort-transaction ((database database)) + (with-slots (transaction transaction-level autocommit) database + (if (plusp transaction-level) + (when (zerop (decf transaction-level)) + (unwind-protect + (case (database-underlying-type database) + (:mssql (execute-command "ROLLBACK TRANSACTION" :database database)) + (t (execute-command "ROLLBACK" :database database))) + (setf autocommit (previous-autocommit transaction)) + (map nil #'funcall (rollback-hooks transaction)))) + (warn 'sql-warning + :message + (format nil "Cannot abort transaction against ~A because there is no transaction in progress." + database))))) + +(defun mark-transaction-committed (database) + (when (and (transaction database) + (not (transaction-status (transaction database)))) + (setf (transaction-status (transaction database)) :committed))) + +(defmacro with-transaction ((&key (database '*default-database*)) &body body) + "Starts a transaction in the database specified by DATABASE, +which is *DEFAULT-DATABASE* by default, and executes BODY within +that transaction. If BODY aborts or throws, DATABASE is rolled +back and otherwise the transaction is committed." + (let ((db (gensym "db-"))) + `(let ((,db ,database)) + (unwind-protect + (prog2 + (database-start-transaction ,db) + (progn + ,@body) + (mark-transaction-committed ,db)) + (if (eq (transaction-status (transaction ,db)) :committed) + (database-commit-transaction ,db) + (database-abort-transaction ,db)))))) + +(defun commit (&key (database *default-database*)) + "If DATABASE, which defaults to *DEFAULT-DATABASE*, is +currently within the scope of a transaction, commits changes made +since the transaction began." + (database-commit-transaction database) + nil) + +(defun rollback (&key (database *default-database*)) + "If DATABASE, which defaults to *DEFAULT-DATABASE*, is +currently within the scope of a transaction, rolls back changes +made since the transaction began." + (database-abort-transaction database) + nil) + +(defun start-transaction (&key (database *default-database*)) + "Starts a transaction block on DATABASE which defaults to +*DEFAULT-DATABASE* and which continues until ROLLBACK or COMMIT +are called." + (unless (in-transaction-p :database database) + (database-start-transaction database)) + nil) + +(defun in-transaction-p (&key (database *default-database*)) + "A predicate to test whether DATABASE, which defaults to +*DEFAULT-DATABASE*, is currently within the scope of a +transaction." + (and database (transaction database) (= (transaction-level database) 1))) + +(defun set-autocommit (value &key (database *default-database*)) + "Turns autocommit off for DATABASE if VALUE is NIL, and +otherwise turns it on. Returns the old value of autocommit flag. +For RDBMS (such as Oracle) which don't automatically commit +changes, turning autocommit on has the effect of explicitly +committing changes made whenever SQL statements are executed. +Autocommit is turned on by default." + (let ((old-value (database-autocommit database))) + (setf (database-autocommit database) value) + (database-autocommit database) + old-value)) + diff --git a/sql/utils.lisp b/sql/utils.lisp new file mode 100644 index 0000000..0196d04 --- /dev/null +++ b/sql/utils.lisp @@ -0,0 +1,511 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: utils.lisp +;;;; Purpose: SQL utility functions +;;;; Programmer: Kevin M. Rosenberg +;;;; Date Started: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-sys) + +(defmacro defaulting (&rest place-value-plist) + "for places and values (as an &rest plist) + if place-n is null set it to value-n" + `(progn + ,@(loop for (place value . rest) on place-value-plist by #'cddr + collect `(unless ,place (setf ,place ,value))))) + +(defmacro pop-n (place &optional (n 1)) + "pops n items off of a list in place and returns their values in a new list + + if n > the length of the list in place, then we return the full list, + setting the place to nil" + `(loop repeat ,n + while ,place + collect (pop ,place))) + +(defun %get-int (v) + (etypecase v + (string (parse-integer v :junk-allowed t)) + (integer v) + (number (truncate v)))) + +(defun dequote (it) + (if (and (listp it) (eql (first it) 'quote)) + (second it) + it)) + +(defvar +whitespace-chars+ + '(#\space #\tab #\newline #\return + ;; Tested: sbcl unicode, allegrocl, openmcl,clisp use #\no-break_space + ;; lispworks uses #\no-break-space + ;; sbcl non-unicode doesn't support no break space + ;; AllegroCL 8-bit strings don't fail on reading #\no-break_space, + ;; but can't represent such a character + ;; CMUCL errors when trying to read #\no-break_space + #+(and lispworks unicode) #\no-break-space + #+(or (and sbcl sb-unicode) (and allegro ics) (and clisp i18n) + (and openmcl openmcl-unicode-strings)) + #\no-break_space + ) + "List of whitespace characters for this lisp implementation.") + +(defun number-to-sql-string (num) + (etypecase num + (integer + (princ-to-string num)) + (rational + (float-to-sql-string (coerce num 'double-float))) + (number + (float-to-sql-string num)))) + +(defun float-to-sql-string (num) + "Convert exponent character for SQL" + (let ((str (write-to-string num :readably t))) + (declare (type string str)) + (cond + ((find #\f str) + (substitute #\e #\f str)) + ((find #\d str) + (substitute #\e #\d str)) + ((find #\l str) + (substitute #\e #\l str)) + ((find #\s str) + (substitute #\e #\S str)) + ((find #\F str) + (substitute #\e #\F str)) + ((find #\D str) + (substitute #\e #\D str)) + ((find #\L str) + (substitute #\e #\L str)) + ((find #\S str) + (substitute #\e #\S str)) + (t + str)))) + +(defun sql-escape (identifier) + "Change hyphens to underscores, ensure string" + (let ((unescaped (etypecase identifier + (symbol (symbol-name identifier)) + (string identifier)))) + (substitute #\_ #\- unescaped))) + +#+lispworks +(defvar +lw-has-without-preemption+ + #-(or lispworks5 lispworks4) nil + #+(or lispworks5 lispworks4) t) +#+lispworks +(defvar +lw-global-lock+ + (unless +lw-has-without-preemption+ + (mp:make-lock :name "CLSQL" :important-p nil :safep t :recursivep nil + :sharing t))) + +(defmacro without-interrupts (&body body) + #+allegro `(mp:without-scheduling ,@body) + #+clisp `(progn ,@body) + #+cmu `(system:without-interrupts ,@body) + #+lispworks + (if +lw-has-without-preemption+ + `(mp:without-preemption ,@body) + `(mp:with-exclusive-lock (+lw-global-lock+) + ,@body)) + #+openmcl `(ccl:without-interrupts ,@body) + #+sbcl `(sb-sys::without-interrupts ,@body)) + +(defun make-process-lock (name) + #+allegro (mp:make-process-lock :name name) + #+cmu (mp:make-lock name) + #+lispworks (mp:make-lock :name name) + #+openmcl (ccl:make-lock name) + #+sb-thread (sb-thread:make-mutex :name name) + #+scl (thread:make-lock name) + #-(or allegro cmu lispworks openmcl sb-thread scl) (declare (ignore name)) + #-(or allegro cmu lispworks openmcl sb-thread scl) nil) + +(defmacro with-process-lock ((lock desc) &body body) + #+(or cmu allegro lispworks openmcl sb-thread) + (declare (ignore desc)) + #+(or allegro cmu lispworks openmcl sb-thread) + (let ((l (gensym))) + `(let ((,l ,lock)) + #+allegro (mp:with-process-lock (,l) ,@body) + #+cmu (mp:with-lock-held (,l) ,@body) + #+openmcl (ccl:with-lock-grabbed (,l) ,@body) + #+lispworks (mp:with-lock (,l) ,@body) + #+sb-thread (sb-thread:with-recursive-lock (,l) ,@body) + )) + #+scl `(thread:with-lock-held (,lock ,desc) ,@body) + #-(or cmu allegro lispworks openmcl sb-thread scl) (declare + (ignore lock desc)) + #-(or cmu allegro lispworks openmcl sb-thread scl) `(progn ,@body)) + +(defun sql-escape-quotes (s) + "Escape quotes for SQL string writing" + (substitute-string-for-char s #\' "''")) + +(defun substitute-string-for-char (procstr match-char subst-str) + "Substitutes a string for a single matching character of a string" + (when procstr + (locally + (declare (type string procstr)) + (let ((pos (position match-char procstr))) + (if pos + (concatenate 'string + (subseq procstr 0 pos) subst-str + (substitute-string-for-char + (subseq procstr (1+ pos)) match-char subst-str)) + procstr))))) + + +(defun position-char (char string start max) + "From KMRCL." + (declare (optimize (speed 3) (safety 0) (space 0)) + (fixnum start max) (simple-string string)) + (do* ((i start (1+ i))) + ((= i max) nil) + (declare (fixnum i)) + (when (char= char (schar string i)) (return i)))) + +(defun delimited-string-to-list (string &optional (separator #\space) + skip-terminal) + "Split a string with delimiter, from KMRCL." + (declare (optimize (speed 3) (safety 0) (space 0) (compilation-speed 0)) + (type string string) + (type character separator)) + (do* ((len (length string)) + (output '()) + (pos 0) + (end (position-char separator string pos len) + (position-char separator string pos len))) + ((null end) + (if (< pos len) + (push (subseq string pos) output) + (when (or (not skip-terminal) (zerop len)) + (push "" output))) + (nreverse output)) + (declare (type fixnum pos len) + (type (or null fixnum) end)) + (push (subseq string pos end) output) + (setq pos (1+ end)))) + +(defun string-to-list-connection-spec (str) + (declare (type string str)) + (let ((at-pos (position-char #\@ str 0 (length str)))) + (cond + ((and at-pos (> (length str) at-pos)) + ;; Connection spec is SQL*NET format + (cons (subseq str (1+ at-pos)) + (delimited-string-to-list (subseq str 0 at-pos) #\/))) + (t + (delimited-string-to-list str #\/))))) + +#+allegro +(eval-when (:compile-toplevel :load-toplevel :execute) + (unless (find-package '#:excl.osi) + (require 'osi))) + +(defun command-output (control-string &rest args) + ;; Concatenates output and error since Lispworks combines + ;; these, thus CLSQL can't depend upon separate results + (multiple-value-bind (output error status) + (apply #'%command-output control-string args) + (values + (concatenate 'string (if output output "") + (if error error "")) + status))) + +(defun read-stream-to-string (in) + (with-output-to-string (out) + (let ((eof (gensym))) + (do ((line (read-line in nil eof) + (read-line in nil eof))) + ((eq line eof)) + (format out "~A~%" line))))) + +;; From KMRCL +(defun %command-output (control-string &rest args) + "Interpolate ARGS into CONTROL-STRING as if by FORMAT, and +synchronously execute the result using a Bourne-compatible shell, +returns (VALUES string-output error-output exit-status)" + (let ((command (apply #'format nil control-string args))) + #+sbcl + (let* ((process (sb-ext:run-program + "/bin/sh" + (list "-c" command) + :input nil :output :stream :error :stream)) + (output (read-stream-to-string (sb-impl::process-output process))) + (error (read-stream-to-string (sb-impl::process-error process)))) + (close (sb-impl::process-output process)) + (close (sb-impl::process-error process)) + (values + output + error + (sb-impl::process-exit-code process))) + + + #+(or cmu scl) + (let* ((process (ext:run-program + "/bin/sh" + (list "-c" command) + :input nil :output :stream :error :stream)) + (output (read-stream-to-string (ext::process-output process))) + (error (read-stream-to-string (ext::process-error process)))) + (close (ext::process-output process)) + (close (ext::process-error process)) + + (values + output + error + (ext::process-exit-code process))) + + #+allegro + (multiple-value-bind (output error status) + (excl.osi:command-output command :whole t) + (values output error status)) + + #+lispworks + ;; BUG: Lispworks combines output and error streams + (let ((output (make-string-output-stream))) + (unwind-protect + (let ((status + (system:call-system-showing-output + command + :shell-type "/bin/sh" + :output-stream output))) + (values (get-output-stream-string output) nil status)) + (close output))) + + #+clisp + ;; BUG: CLisp doesn't allow output to user-specified stream + (values + nil + nil + (ext:run-shell-command command :output :terminal :wait t)) + + #+openmcl + (let* ((process (ccl:run-program + "/bin/sh" + (list "-c" command) + :input nil :output :stream :error :stream + :wait t)) + (output (read-stream-to-string (ccl::external-process-output-stream process))) + (error (read-stream-to-string (ccl::external-process-error-stream process)))) + (close (ccl::external-process-output-stream process)) + (close (ccl::external-process-error-stream process)) + (values output + error + (nth-value 1 (ccl::external-process-status process)))) + + #-(or openmcl clisp lispworks allegro scl cmu sbcl) + (error "COMMAND-OUTPUT not implemented for this Lisp") + + )) + + +;; From KMRCL +(defmacro in (obj &rest choices) + (let ((insym (gensym))) + `(let ((,insym ,obj)) + (or ,@(mapcar #'(lambda (c) `(eql ,insym ,c)) + choices))))) + +;; From KMRCL +(defun substitute-char-string (procstr match-char subst-str) + "Substitutes a string for a single matching character of a string" + (substitute-chars-strings procstr (list (cons match-char subst-str)))) + +(defun replaced-string-length (str repl-alist) + (declare (simple-string str) + (optimize (speed 3) (safety 0) (space 0))) + (do* ((i 0 (1+ i)) + (orig-len (length str)) + (new-len orig-len)) + ((= i orig-len) new-len) + (declare (fixnum i orig-len new-len)) + (let* ((c (char str i)) + (match (assoc c repl-alist :test #'char=))) + (declare (character c)) + (when match + (incf new-len (1- (length + (the simple-string (cdr match))))))))) + + +(defun substitute-chars-strings (str repl-alist) + "Replace all instances of a chars with a string. repl-alist is an assoc +list of characters and replacement strings." + (declare (simple-string str) + (optimize (speed 3) (safety 0) (space 0))) + (do* ((orig-len (length str)) + (new-string (make-string (replaced-string-length str repl-alist))) + (spos 0 (1+ spos)) + (dpos 0)) + ((>= spos orig-len) + new-string) + (declare (fixnum spos dpos) (simple-string new-string)) + (let* ((c (char str spos)) + (match (assoc c repl-alist :test #'char=))) + (declare (character c)) + (if match + (let* ((subst (cdr match)) + (len (length subst))) + (declare (fixnum len) + (simple-string subst)) + (dotimes (j len) + (declare (fixnum j)) + (setf (char new-string dpos) (char subst j)) + (incf dpos))) + (progn + (setf (char new-string dpos) c) + (incf dpos)))))) + + +(defun getenv (var) + "Return the value of the environment variable." + #+allegro (sys::getenv (string var)) + #+clisp (ext:getenv (string var)) + #+(or cmu scl) + (cdr (assoc (string var) ext:*environment-list* :test #'equalp + :key #'string)) + #+lispworks (lw:environment-variable (string var)) + #+ccl (ccl::getenv var) + #+sbcl (sb-ext:posix-getenv var)) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (when (char= #\a (schar (symbol-name '#:a) 0)) + (pushnew :clsql-lowercase-reader *features*))) + +(defun symbol-name-default-case (str) + #-clsql-lowercase-reader + (string-upcase str) + #+clsql-lowercase-reader + (string-downcase str)) + +(defun convert-to-db-default-case (str database) + (if database + (case (db-type-default-case (database-underlying-type database)) + (:upper (string-upcase str)) + (:lower (string-downcase str)) + (t str)) + ;; Default CommonSQL behavior is to upcase strings + (string-upcase str))) + +(defun ensure-keyword (name) + "Returns keyword for a name." + (etypecase name + (keyword name) + (string (nth-value 0 (intern (symbol-name-default-case name) :keyword))) + (symbol (nth-value 0 (intern (symbol-name name) :keyword))))) + +(eval-when (:compile-toplevel :load-toplevel :execute) + (setq cl:*features* (delete :clsql-lowercase-reader cl:*features*))) + +(defun replace-all (string part replacement &key (test #'char=) stream) + "Returns a new string in which all the occurences of the part +is replaced with replacement. [FROM http://cl-cookbook.sourceforge.net/strings.html#manip]" + (let ((out (or stream (make-string-output-stream)))) + (loop with part-length = (length part) + for old-pos = 0 then (+ pos part-length) + for pos = (search part string + :start2 old-pos + :test test) + do (write-string string out + :start old-pos + :end (or pos (length string))) + when pos do (write-string replacement out) + while pos) + (unless stream + (get-output-stream-string out)))) + +(defun read-decimal-value (string) + (let* ((comma 0) + (dot 0) + (last)) + (loop for c across string + do (case c + (#\. (incf dot) (setf last 'dot)) + (#\, (incf comma) (setf last 'comma)))) + (let* ((bag (if (and (eql last 'dot) (eql dot 1)) + ".0123456789+-" + ",0123456789+-")) + (clean (with-output-to-string (out) + (loop for c across string + do (when (find c bag :test #'char=) + (write-char c out)))))) + (if (and (eql last 'dot) (eql dot 1)) + (decimals:parse-decimal-number clean) + (decimals:parse-decimal-number + clean :decimal-separator #\,))))) + + +(defun filter-plist (plist &rest keys-to-remove) + "Returns a copy of the given plist with indicated key-value pairs +removed. keys are searched with #'MEMBER" + (declare (dynamic-extent keys-to-remove)) + (when plist + (loop for (k v . rest) = plist then rest + unless (member k keys-to-remove) + collect k and collect v + while rest))) + +(defmacro make-weak-hash-table (&rest args) + "Creates a weak hash table for use in a cache." + `(progn + + ;;NB: These are generally used for caches that may not have an alternate + ;;clearing mechanism. If you are on an implementation that doesn't support + ;;weak hash tables then you're memory may accumulate. + + #-(or sbcl allegro clisp lispworks ccl) + (warn "UNSAFE! use of weak hash on implementation without support. (see clsql/sql/utils.lisp to add)") + + (make-hash-table + #+allegro :values #+allegro :weak + #+clisp :weak #+clisp :value + #+lispworks :weak-kind #+lispworks :value + #+sbcl :weakness #+sbcl :value + #+ccl :weak #+ccl :value + ,@args) + )) + +(defun to-slot-name (slot) + "try to turn what we got representing the slot into a slot name" + (etypecase slot + (symbol slot) + (slot-definition (slot-definition-name slot)))) + +(defun to-class (it) + (etypecase it + (class it) + (symbol (find-class it)) + (standard-object (class-of it)))) + +(defun to-class-name (o) + (etypecase o + (symbol o) + (standard-class (class-name o)) + (standard-object (class-name (class-of o))))) + +(defun easy-slot-value (obj slot) + "like slot-value except it accepts slot-names or defs + and returns nil when the slot is unbound" + (let ((n (to-slot-name slot))) + (when (and obj (slot-boundp obj n)) + (slot-value obj n)))) + +(defun (setf easy-slot-value) (new obj slot) + "like slot-value except it accepts slot-names or defs" + (setf (slot-value obj (to-slot-name slot)) new)) + +(defun delist-if-single (x) + "if this is a single item in a list return it" + (if (and (listp x) (= 1 (length x))) + (first x) + x)) diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..695e656 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,24 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL tests +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file is part of CLSQL +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +.PHONY: distclean +distclean: clean + diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..aa6a9a6 --- /dev/null +++ b/tests/README @@ -0,0 +1,124 @@ +* RUNNING THE REGRESSION SUITE + +This test suite looks for a configuration file named +".clsql-test.config" located in the users home directory. This file +contains a single assoc-list that specifies the connection specs for +each database type to be tested. There is an example file in +contained in CLSQL's examples directory. + +These tests require the downloading of the rt package from +http://files.kpe.io/. + +Load clsql.asd or put it somewhere where ASDF can find it +and call: + +(asdf:oos 'asdf:test-op 'clsql) + +The test suite will then automatically run on all of the backends that +you have defined in .clsql-test.config + +* REGRESSION TEST SUITE GOALS + +The intent of this test suite is to provide sufficient coverage for +the system to support the following: + +** Refactoring and Redesign of particular subsystems + +Refactoring and redesign efforts are normally restricted to a single +subsystem, or perhaps to interdependent subsystems. In such cases, a +set of regression tests which excercise the existing interface of the +rest of CLSQL to the changing subsystems should be in place and passing +before the coding starts. + +** Ensuring portability and Supporting new ports. + +The more coverage the test suite provides the easier portability is to +maintain, particularly if we have instances of the test suite running +against the head on the supporting lisp environment/OS/hardware/DBMS +combinations. Since no individual within the project has the ability +to run all of those combinations themselves, we are dependent upon some +informal coordination between the mintainers of the various ports. + +** Adding new RDBMS backends + +The entire CLSQL DBMS interface needs to be excercised by the test +suite, such that a new RDBMS backend that passes all the tests can be +reasonably assured of working with the CLSQL layers above that. These +tests should also serve as impromptu documentation for the details of +that interface and what it expects frothe RDBMS driver layers. + +** Bug identification and QA + +As new bugs are identified, they should have a regression test written +which excercises them. This is to ensue that we donot start +backtracking. These tests by theselves are also very valuable for +developers, so even if you cannot fix a bug yourself, providing a +testto excercise it greatly reduces the amount of timea developer must +spend finding the bug prior to fixing it. + + +* TEST DESIGN ISSUES + +** Multiple RDBMS Issues + +CLSQL supports several RDBMS backends, and it should be possible to run +every test against all of them. However, there are some features +which we want tests for but which are not implemented on several of +the backends. + +** Test Hygiene + +Tests should be able to be run multiple times against the same +database. It is also important that they clean up after themselves +when they create tables, sequences or other pesistent entities in the +RDBMS backends, because often there are limits to the number of those +thatcan exist at one time, and it also makes debuging thru the SQL +monitors difficult when there aretons of unused tables lying around. + +If test need to load large datasets, they should have a mechanism to +ensure the dataset is loaded just once, and not with every test run. + +Lastly, because there are various idiosyncracies with RDBMSs, please +ensure that you run the entire test suite once when you write your +tests, to ensure that your test does not leave some state behind which +causes other tests to fail. + +** Test Run Configuration + +The file test-init.lisp defines several variables which can be used to +control the connection dictionary of the database against which tests +will be run. + + +* DATABASE CONNECTIONS/LIFECYCLE + +** CreateDB + *** Without existing DB + *** With existing DB and use old + *** With existing DB and use new + *** Error if existing DB + +** Data Definition + *** Create Tables/Views/Sequences/Indexes -- Should cover creation of + tables with all supported types of fields. + *** Delete Tables/Views/Sequences/Indexes + *** Inspection of Tables and attributes, including types + +** Data Manipulation + *** Update + *** Insert + *** Delete + *** Query + +** Functional Interface + *** Creation/Modification of SQL expressions + *** Querying + +** Embedded SQL syntax + *** Excercise all sql operators + +** Object Interface + *** View class definition + *** Object creation/manipulation/deletion + *** Inter-object Relations + diff --git a/tests/benchmarks.lisp b/tests/benchmarks.lisp new file mode 100644 index 0000000..4e52890 --- /dev/null +++ b/tests/benchmarks.lisp @@ -0,0 +1,87 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: benchmarks.lisp +;;;; Purpose: Time performance tests for CLSQL +;;;; Authors: Kevin M. Rosenberg +;;;; Created: March 5, 2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + + +(in-package #:clsql-tests) + +(defun run-benchmarks-append-report-file (report-file) + (run-function-append-report-file 'run-benchmarks report-file)) + +(clsql:def-view-class bench () + ((a :initarg :a + :type integer) + (b :initarg :b + :type (string 100)) + (c :initarg :c + :type float))) + +(defun run-benchmarks (&key (report-stream *standard-output*) (sexp-report-stream nil) (count 10000)) + (let ((specs (read-specs)) + (*report-stream* report-stream) + (*sexp-report-stream* sexp-report-stream)) + (unless specs + (warn "Not running benchmarks because test configuration file is missing") + (return-from run-benchmarks :skipped)) + (load-necessary-systems specs) + (dolist (db-type +all-db-types+) + (dolist (spec (db-type-spec db-type specs)) + (do-benchmarks-for-backend db-type spec count)))) + (values)) + +(defun do-benchmarks-for-backend (db-type spec count) + (test-setup-database db-type spec) + (write-report-banner "Benchmarks" db-type *report-stream* + (database-name-from-spec spec db-type)) + + (create-view-from-class 'bench) + (benchmark-init) + (benchmark-selects count) + (drop-view-from-class 'bench)) + +(defun benchmark-init () + (dotimes (i 10) + (execute-command "INSERT INTO BENCH (A,B,C) VALUES (123,'A Medium size string',3.14159)"))) + +(defun benchmark-selects (n) + (let ((*trace-output* *report-stream*)) + (format *report-stream* "~&~%*** QUERY ***~%") + (time + (dotimes (i n) + (query "SELECT * FROM BENCH"))) + (format *report-stream* "~&~%*** QUERY WITH RESULT-TYPES NIL ***~%") + (time + (dotimes (i n) + (query "SELECT * FROM BENCH" :result-types nil))) + (format *report-stream* "~&~%*** QUERY WITH FIELD-NAMES NIL ***~%") + (time + (dotimes (i n) + (query "SELECT * FROM BENCH" :field-names nil))) + + (with-dataset *ds-employees* + (format *report-stream* "~&~%*** JOINED OBJECT QUERY RETRIEVAL IMMEDIATE ***~%") + (time + (dotimes (i (truncate n 10)) + (mapcar #'(lambda (ea) (slot-value ea 'address)) (select 'employee-address :flatp t)))) + + (format *report-stream* "~&~%*** JOINED OBJECT QUERY RETRIEVAL DEFERRED ***~%") + (let* ((slotdef (find 'address (clsql-sys::class-slots (find-class 'employee-address)) + :key #'clsql-sys::slot-definition-name)) + (dbi (when slotdef (clsql-sys::view-class-slot-db-info slotdef)))) + (setf (gethash :retrieval dbi) :deferred) + (time + (dotimes (i (truncate n 10)) + (mapcar #'(lambda (ea) (slot-value ea 'address)) (select 'employee-address :flatp t)))) + (setf (gethash :retrieval dbi) :immediate))))) diff --git a/tests/datasets.lisp b/tests/datasets.lisp new file mode 100644 index 0000000..42698ec --- /dev/null +++ b/tests/datasets.lisp @@ -0,0 +1,141 @@ +;;;; Proposed new file in clsql-tests to enable abstracting datasets for reuse. +;;;; +;;;; The core is def-datset and with-dataset that respectively define a set, +;;;; and enable one for a dynamic scope. Datasets will normally be setup and +;;;; torn down in the scope of one test, which may impose a computation +;;;; overhead, but enables simpler tests by not worrying about side-effects +;;;; between tests. +;;;; +;;;; In general datasets should be database agnostic, but because the code +;;;; is only run in the scope of a test, if a test is excluded for a backend +;;;; or some other reason then it is never run hence doesn't cause problems. + +(in-package #:clsql-tests) + +(defparameter *dataset-debug-on-error* nil + "If we get an error while loading or cleaning up the dataset, +should we debug (T) or just print and quit.") + +(defun generic-error (e) + (when (and *dataset-debug-on-error* + *debugger-hook*) + (invoke-debugger e)) + (fresh-line *error-output*) + (princ e *error-output*) + (throw 'quit-dataset e)) + +(defmacro def-dataset (name &body body) + "Define a dataset" + ;;probably just shove this into a param, perhaps a marginal + ;; bit of processing first. + `(defparameter ,name ',body)) + +(defmacro with-dataset (name &body body) + "Use a dataset in a dynamic scope, e.g. a single test. +1. Before the body: + * :setup is run + * :data is loaded +2. Body +3. :cleanup always happens" + `(catch 'quit-dataset + (unwind-protect + (progn + (restart-case (%dataset-init ,name) + (retry-dataset-init () + :report ,(format nil "Retry dataset('~a) init: (with any dataset changes)" + (symbol-name name)) + (%dataset-init ,name)) + (skip-this-test () + :report "FAIL and run the next test" + (throw 'quit-dataset :data-set-failure))) + ,@body) + (%dataset-cleanup ,name)))) + + +(defun %dataset-dispatch (arg) + "For use with def-dataset and with-dataset, tries to DWIM." + (etypecase arg + (string (clsql-sys:execute-command arg)) ;treat it as a sql command. + ((or function symbol) (funcall arg)) ;run functions + (list + (case (first arg) + ((function lambda) (%dataset-dispatch (eval arg))) ;#' forms, lambdas + (progn (mapc #'%dataset-dispatch (rest arg))) ; (progn "asdf" "ff") + (ignore-errors (ignore-errors (mapc #'%dataset-dispatch (rest arg)))) + (t (mapc #'%dataset-dispatch arg))) ;otherwise implicit progn + ))) + +(defun %dataset-init (name) + "Run initialization code and fill database for given dataset." + ;;find items that looks like '(:setup ...), + ;; dispatch the rest. + (let ((*backend-warning-behavior* + (typecase *default-database* + (clsql-sys:generic-postgresql-database + :ignore) + (t *backend-warning-behavior*))) + (setup (rest (find :setup name :key #'first))) + (sqldata (rest (find :sqldata name :key #'first))) + (objdata (rest (find :objdata name :key #'first)))) + (when setup + (handler-bind ((warning + (lambda (c) + (when (eql :ignore *backend-warning-behavior*) + (muffle-warning c))))) + (%dataset-dispatch setup))) + (when sqldata + ;;run raw sql insert statements + (destructuring-bind (table-name columns &rest values-list) sqldata + (dolist (v values-list) + (clsql-sys:execute-command + (format nil + "INSERT INTO ~a (~a) VALUES (~a)" + table-name columns v))))) + (when objdata + ;;presumed to be view-class objects, force them to insert. + (dolist (o objdata) + (setf (slot-value o 'clsql-sys::view-database) nil) + (clsql-sys:update-records-from-instance o))))) + +(defun %dataset-cleanup (name) + "Run cleanup code associated with the given dataset." + (restart-case + (handler-bind ((error #'generic-error)) + (let ((cleanup (rest (find :cleanup name :key #'first)))) + (when cleanup + (%dataset-dispatch cleanup)))) + (retry-dataset-cleanup () + :report "Retry dataset cleanup (with any dataset changes)" + (%dataset-cleanup name)) + (skip-cleanup () nil))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Example Test Code + +;;incomplete example taken from test-init +;; (def-dataset *employees* +;; (:setup "CREATE TABLE employee +;; ( +;; emplid integer NOT NULL, +;; groupid integer NOT NULL, +;; first_name character varying(30), +;; last_name character varying(30), +;; email character varying(100), +;; ecompanyid integer, +;; managerid integer, +;; height double, +;; married boolean, +;; birthday timestamp without time zone, +;; bd_utime bigint, +;; CONSTRAINT employeepk PRIMARY KEY (emplid, groupid), +;; CONSTRAINT employee_emplid_key UNIQUE (emplid) +;; ) +;; ") +;; ;;alternatively setup can still be done as +;; ;;(:setup #'(lambda () (create-view-from-class ...))) +;; (:sqldata "employees" "emplid,groupid,married,height,first_name,last_name" +;; "1,1,false,1.5,'Napolean', 'Bonaparte'" +;; (format nil "1,1,true,~a,'Vladimir','Lenin'" (1+ (random 1.00)))) +;; (:cleanup "DROP TABLE EMPLOYEES")) + diff --git a/tests/ds-artists.lisp b/tests/ds-artists.lisp new file mode 100644 index 0000000..f4d3271 --- /dev/null +++ b/tests/ds-artists.lisp @@ -0,0 +1,31 @@ +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) + +(defparameter artist1 nil) + +(def-view-class artist () + ((artist_id :accessor artist_id :initarg :id + :type integer :db-kind :key :db-constraints (:not-null :auto-increment) + :autoincrement-sequence 'artist_artist_id_seq) + (name :accessor name :initarg :name :type (varchar 20)) + (genre :accessor genre :initarg :genre :type (varchar 10) :db-constraints (:default "'Unknown'")))) + +(defun initialize-ds-artists () + (mapc #'clsql:create-view-from-class + '(artist)) + + (setq *test-start-utime* (get-universal-time)) + (let* ((*db-auto-sync* nil)) + (setf artist1 (make-instance 'artist + :name "Mogwai")))) + +(def-dataset *ds-artists* + (:setup initialize-ds-artists) + (:cleanup (lambda () + (mapc #'clsql-sys:drop-view-from-class + '(artist)) + (ignore-errors + (mapc #'clsql-sys:drop-sequence + (list "artist_artist_id_seq")))))) + diff --git a/tests/ds-employees.lisp b/tests/ds-employees.lisp new file mode 100644 index 0000000..2611053 --- /dev/null +++ b/tests/ds-employees.lisp @@ -0,0 +1,405 @@ +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) +(defparameter company1 nil) +(defparameter employee1 nil) +(defparameter employee2 nil) +(defparameter employee3 nil) +(defparameter employee4 nil) +(defparameter employee5 nil) +(defparameter employee6 nil) +(defparameter employee7 nil) +(defparameter employee8 nil) +(defparameter employee9 nil) +(defparameter employee10 nil) +(defparameter address1 nil) +(defparameter address2 nil) +(defparameter address3 nil) +(defparameter employee-address1 nil) +(defparameter employee-address2 nil) +(defparameter employee-address3 nil) +(defparameter employee-address4 nil) +(defparameter employee-address5 nil) +(defparameter employee-address6 nil) + +(defclass thing () + ((extraterrestrial :initform nil :initarg :extraterrestrial))) + +(def-view-class person (thing) + ((height :db-kind :base :accessor height :type float + :initarg :height) + (married :db-kind :base :accessor married :type boolean + :initarg :married) + (birthday :type clsql:wall-time :initarg :birthday) + (bd-utime :type clsql:universal-time :initarg :bd-utime) + (hobby :db-kind :virtual :initarg :hobby :initform nil))) + +(def-view-class employee (person) + ((emplid + :db-kind :key + :db-constraints (:not-null :unique) + :type integer + :initarg :emplid) + (groupid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :groupid) + (title + :accessor title + :type symbol + :initarg :title) + (first-name + :accessor first-name + :type (varchar 30) + :initarg :first-name) + (last-name + :accessor last-name + :type (varchar 30) + :initarg :last-name) + (email + :accessor employee-email + :type (varchar 100) + :initarg :email) + (ecompanyid + :type integer + :initarg :companyid) + (company + :accessor employee-company + :db-kind :join + :db-info (:join-class company + :home-key ecompanyid + :foreign-key companyid + :set nil)) + (managerid + :type integer + :initarg :managerid) + (manager + :accessor employee-manager + :db-kind :join + :db-info (:join-class employee + :home-key managerid + :foreign-key emplid + :set nil)) + (addresses + :accessor employee-addresses + :db-kind :join + :db-info (:join-class employee-address + :home-key emplid + :foreign-key aemplid + :target-slot address + :set t))) + (:base-table employee)) + +(def-view-class company () + ((companyid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :companyid) + (groupid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :groupid) + (name + :type (varchar 100) + :initarg :name) + (presidentid + :type integer + :initarg :presidentid) + (president + :reader president + :db-kind :join + :db-info (:join-class employee + :home-key presidentid + :foreign-key emplid + :set nil)) + (employees + :reader company-employees + :db-kind :join + :db-info (:join-class employee + :home-key (companyid groupid) + :foreign-key (ecompanyid groupid) + :set t)))) + +(def-view-class address () + ((addressid + :db-kind :key + :db-constraints :not-null + :type integer + :initarg :addressid) + (street-number + :type integer + :initarg :street-number) + (street-name + :type (varchar 30) + :void-value "" + :initarg :street-name) + (city + :column "city_field" + :void-value "no city" + :type (varchar 30) + :initarg :city) + (postal-code + :column zip + :type integer + :void-value 0 + :initarg :postal-code)) + (:base-table addr)) + +;; many employees can reside at many addressess +(def-view-class employee-address () + ((aemplid :type integer :initarg :emplid) + (aaddressid :type integer :initarg :addressid) + (verified :type boolean :initarg :verified) + (address :db-kind :join + :db-info (:join-class address + :home-key aaddressid + :foreign-key addressid + :retrieval :immediate)) + (employee :db-kind :join + :db-info (:join-class employee + :home-key aemplid + :foreign-key emplid + :retrieval :immediate))) + (:base-table "ea_join")) + +(def-view-class deferred-employee-address () + ((aemplid :type integer :initarg :emplid) + (aaddressid :type integer :initarg :addressid) + (verified :type boolean :initarg :verified) + (address :db-kind :join + :db-info (:join-class address + :home-key aaddressid + :foreign-key addressid + :retrieval :deferred + :set nil))) + (:base-table "ea_join")) + + + +(defun initialize-ds-employees () + ;; (start-sql-recording :type :both) + (mapc #'clsql:create-view-from-class + '(employee company address employee-address)) + + (setq *test-start-utime* (get-universal-time)) + (let* ((*db-auto-sync* t) + (now-time (clsql:utime->time *test-start-utime*))) + (setf company1 (make-instance 'company + :presidentid 1 + :companyid 1 + :groupid 1 + :name "Widgets Inc.") + employee1 (make-instance 'employee + :emplid 1 + :groupid 1 + :married t + :title 'supplicant + :height (1+ (random 1.00)) + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Vladimir" + :last-name "Lenin" + :email "lenin@soviet.org" + :companyid 1) + employee2 (make-instance 'employee + :emplid 2 + :groupid 1 + :title :adherent + :height (1+ (random 1.00)) + :married t + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Josef" + :last-name "Stalin" + :email "stalin@soviet.org" + :managerid 1 + :companyid 1) + employee3 (make-instance 'employee + :emplid 3 + :groupid 1 + :title 'cl-user::novice + :height (1+ (random 1.00)) + :married t + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Leon" + :last-name "Trotsky" + :email "trotsky@soviet.org" + :managerid 1 + :companyid 1) + employee4 (make-instance 'employee + :emplid 4 + :groupid 1 + :height (1+ (random 1.00)) + :married nil + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Nikita" + :last-name "Kruschev" + :email "kruschev@soviet.org" + :managerid 1 + :companyid 1) + employee5 (make-instance 'employee + :emplid 5 + :groupid 1 + :married nil + :height (1+ (random 1.00)) + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Leonid" + :last-name "Brezhnev" + :email "brezhnev@soviet.org" + :managerid 1 + :companyid 1) + employee6 (make-instance 'employee + :emplid 6 + :groupid 1 + :married nil + :height (1+ (random 1.00)) + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Yuri" + :last-name "Andropov" + :email "andropov@soviet.org" + :managerid 1 + :companyid 1) + employee7 (make-instance 'employee + :emplid 7 + :groupid 1 + :height (1+ (random 1.00)) + :married nil + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Konstantin" + :last-name "Chernenko" + :email "chernenko@soviet.org" + :managerid 1 + :companyid 1) + employee8 (make-instance 'employee + :emplid 8 + :groupid 1 + :height (1+ (random 1.00)) + :married nil + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Mikhail" + :last-name "Gorbachev" + :email "gorbachev@soviet.org" + :managerid 1 + :companyid 1) + employee9 (make-instance 'employee + :emplid 9 + :groupid 1 + :married nil + :height (1+ (random 1.00)) + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Boris" + :last-name "Yeltsin" + :email "yeltsin@soviet.org" + :managerid 1 + :companyid 1) + employee10 (make-instance 'employee + :emplid 10 + :groupid 1 + :married nil + :height (1+ (random 1.00)) + :bd-utime *test-start-utime* + :birthday now-time + :first-name "Vladimir" + :last-name "Putin" + :email "putin@soviet.org" + :managerid 1 + :companyid 1) + address1 (make-instance 'address + :addressid 1 + :street-number 10 + :street-name "Park Place" + :city "Leningrad" + :postal-code 123) + address2 (make-instance 'address + :addressid 2) + address3 (make-instance 'address + :addressid 3) + employee-address1 (make-instance 'employee-address + :emplid 1 + :addressid 1 + :verified t) + employee-address2 (make-instance 'employee-address + :emplid 2 + :addressid 2 + :verified t) + employee-address3 (make-instance 'employee-address + :emplid 3 + :addressid 1 + :verified nil) + employee-address4 (make-instance 'employee-address + :emplid 1 + :addressid 2 + :verified nil) + employee-address5 (make-instance 'employee-address + :emplid 3 + :addressid 2) + employee-address6 (make-instance 'employee-address + :emplid 4 + :addressid 3))) + + ;; sleep to ensure birthdays are no longer at current time + ;(sleep 1) ;want to find the test that depends on it, put the sleep there. + + #|| + ;; Lenin manages everyone ; + (clsql:add-to-relation employee2 'manager employee1) + (clsql:add-to-relation employee3 'manager employee1) + (clsql:add-to-relation employee4 'manager employee1) + (clsql:add-to-relation employee5 'manager employee1) + (clsql:add-to-relation employee6 'manager employee1) + (clsql:add-to-relation employee7 'manager employee1) + (clsql:add-to-relation employee8 'manager employee1) + (clsql:add-to-relation employee9 'manager employee1) + (clsql:add-to-relation employee10 'manager employee1) + ;; Everyone works for Widgets Inc. ; + (clsql:add-to-relation company1 'employees employee1) + (clsql:add-to-relation company1 'employees employee2) + (clsql:add-to-relation company1 'employees employee3) + (clsql:add-to-relation company1 'employees employee4) + (clsql:add-to-relation company1 'employees employee5) + (clsql:add-to-relation company1 'employees employee6) + (clsql:add-to-relation company1 'employees employee7) + (clsql:add-to-relation company1 'employees employee8) + (clsql:add-to-relation company1 'employees employee9) + (clsql:add-to-relation company1 'employees employee10) + ;; Lenin is president of Widgets Inc. ; + (clsql:add-to-relation company1 'president employee1) + ||# + + ;; store these instances + #|| + (clsql:update-records-from-instance employee1) + (clsql:update-records-from-instance employee2) + (clsql:update-records-from-instance employee3) + (clsql:update-records-from-instance employee4) + (clsql:update-records-from-instance employee5) + (clsql:update-records-from-instance employee6) + (clsql:update-records-from-instance employee7) + (clsql:update-records-from-instance employee8) + (clsql:update-records-from-instance employee9) + (clsql:update-records-from-instance employee10) + (clsql:update-records-from-instance company1) + (clsql:update-records-from-instance address1) + (clsql:update-records-from-instance address2) + ||# + ) + + + (def-dataset *ds-employees* + (:setup initialize-ds-employees) + (:cleanup (lambda () + (mapc #'clsql-sys:drop-view-from-class + '(employee company address employee-address)) + (ignore-errors + (clsql-sys:execute-command "DROP TABLE ea_join"))))) + diff --git a/tests/ds-nodes.lisp b/tests/ds-nodes.lisp new file mode 100644 index 0000000..098c742 --- /dev/null +++ b/tests/ds-nodes.lisp @@ -0,0 +1,118 @@ +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) + +(defparameter basenode nil) +(defparameter derivednode1 nil) +(defparameter derivednode2 nil) +(defparameter node nil) +(defparameter setting1 nil) +(defparameter setting2 nil) +(defparameter user1 nil) +(defparameter user2 nil) +(defparameter theme1 nil) +(defparameter theme2 nil) +(defparameter loc1 nil) +(defparameter loc2 nil) +(defparameter subloc1 nil) +(defparameter subloc2 nil) + + +;; classes for testing the normalizedp stuff +(def-view-class node () + ((node-id :accessor node-id :initarg :node-id + :type integer :db-kind :key + :db-constraints (:not-null :auto-increment)) + (title :accessor title :initarg :title :type (varchar 240)) + (createtime :accessor createtime :initarg :createtime :type wall-time + :db-constraints (:not-null) :initform (get-time)) + (modifiedtime :accessor modifiedtime :initarg :modifiedtime :type wall-time + :initform (make-time :year 1900 :month 1 :day 1)))) + +(def-view-class setting (node) + ((setting-id :accessor setting-id :initarg :setting-id + :type integer :db-kind :key :db-constraints (:not-null )) + (vars :accessor vars :initarg :vars :type (varchar 240))) + (:normalizedp t)) + +(def-view-class user (node) + ((user-id :accessor user-id :initarg :user-id + :type integer :db-kind :key :db-constraints (:not-null )) + (nick :accessor nick :initarg :nick :type (varchar 64))) + (:base-table "nodeuser") + (:normalizedp t)) + +(def-view-class theme (setting) + ((theme-id :accessor theme-id :initarg :theme-id + :type integer :db-kind :key :db-constraints (:not-null )) + (doc :accessor doc :initarg :doc :type (varchar 240))) + (:normalizedp t)) + +;; A class that uses only a superclass db table +(def-view-class location (node) + () + (:base-table node) + (:normalizedp t)) + +(def-view-class subloc (location) + ((subloc-id :accessor subloc-id :initarg :subloc-id + :type integer :db-kind :key :db-constraints (:not-null )) + (loc :accessor loc :initarg :loc :type (varchar 64))) + (:normalizedp t)) + + + +(defun initialize-ds-nodes () + ;; (start-sql-recording :type :both) + (mapc #'clsql:create-view-from-class + '(node setting user theme location subloc)) + + (setq *test-start-utime* (get-universal-time)) + (let* ((*db-auto-sync* t)) + (setf node (make-instance 'node + :title "Bare node") + setting1 (make-instance 'setting + :title "Setting1" + :vars "var 1") + setting2 (make-instance 'setting + :title "Setting2" + :vars "var 2") + user1 (make-instance 'user + :title "user-1" + :nick "first user") + user2 (make-instance 'user + :title "user-2" + :nick "second user") + theme1 (make-instance 'theme + :title "theme-1" + :vars "empty" + :doc "first theme") + theme2 (make-instance 'theme + :title "theme-2" + :doc "second theme") + loc1 (make-instance 'location + :title "location-1") + loc2 (make-instance 'location + :title "location-2") + subloc1 (make-instance 'subloc + :title "subloc-1" + :loc "a subloc") + subloc2 (make-instance 'subloc + :title "subloc-2" + :loc "second subloc")))) + + + + + (def-dataset *ds-nodes* + (:setup initialize-ds-nodes) + (:cleanup (lambda () + (mapc #'clsql-sys:drop-view-from-class + '(node setting user theme location subloc)) + (ignore-errors + (clsql-sys:execute-command "DROP TABLE nodeuser") + (mapc #'clsql-sys:drop-sequence + '(node_node_id_seq setting_setting_id_seq subloc_subloc_id_seq + theme_theme_id_seq nodeuser_user_id_seq) + ))))) + diff --git a/tests/package.lisp b/tests/package.lisp new file mode 100644 index 0000000..f420b10 --- /dev/null +++ b/tests/package.lisp @@ -0,0 +1,33 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: package.lisp +;;;; Purpose: Package definition for CLSQL test suite +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + + +(in-package #:cl-user) + +(defpackage #:clsql-tests + (:use #:clsql #:common-lisp #:rtest) + (:shadowing-import-from #:clsql-sys #:%get-int ) + (:export + #:run-tests + #:run-tests-append-report-file + #:run-benchmarks + #:run-benchmarks-append-report-file + #:summarize-test-report + #:test-connect + #:test-setup-database + ) + (:documentation "Regression tests for CLSQL.")) + diff --git a/tests/test-basic.lisp b/tests/test-basic.lisp new file mode 100644 index 0000000..4d277e3 --- /dev/null +++ b/tests/test-basic.lisp @@ -0,0 +1,314 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-basic.lisp +;;;; Purpose: Tests for clsql string-based queries and result types +;;;; Author: Kevin M. Rosenberg +;;;; Created: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + + +(setq *rt-basic* + '( + (deftest :basic/type/1 + (with-dataset *ds-basic* + (let ((results '())) + (dolist (row (query "select * from TYPE_TABLE" :result-types :auto) + results) + (destructuring-bind (int float str) row + (push (list (integerp int) + (typep float 'double-float) + (stringp str)) + results))))) + ((t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t))) + + (deftest :basic/type/2 + (with-dataset *ds-basic* + (let ((results '())) + (dolist (row (query "select * from TYPE_TABLE" :result-types :auto) + results) + (destructuring-bind (int float str) row + (setq results + (cons (list (double-float-equal + (transform-float-1 int) + float) + (double-float-equal + (parse-double str) + float)) + results)))) + results)) + ((t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t))) + + (deftest :basic/select/1 + (with-dataset *ds-basic* + (let ((rows (query "select * from TYPE_TABLE" :result-types :auto))) + (values + (length rows) + (length (car rows))))) + 11 3) + + (deftest :BASIC/SELECT/2 + (with-dataset *ds-basic* + (let ((results '())) + (dolist (row (query "select * from TYPE_TABLE" :result-types nil) + results) + (destructuring-bind (int float str) row + (push (list (stringp int) + (stringp float) + (stringp str)) + results))))) + ((t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t) (t t t))) + + (deftest :basic/select/3 + (with-dataset *ds-basic* + (let ((results '())) + (dolist (row (query "select * from TYPE_TABLE" :result-types nil) + results) + (destructuring-bind (int float str) row + (push (list (double-float-equal + (transform-float-1 (parse-integer int)) + (parse-double float)) + (double-float-equal + (parse-double str) + (parse-double float))) + results))))) + ((t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t))) + + (deftest :basic/map/1 + (with-dataset *ds-basic* + (let ((results '()) + (rows (map-query 'vector #'identity "select * from TYPE_TABLE" + :result-types nil))) + (declare (type (simple-array list (*)) rows)) + (dotimes (i (length rows) results) + (push + (list + (listp (aref rows i)) + (length (aref rows i)) + (eql (- i 5) + (parse-integer (first (aref rows i)) + :junk-allowed nil)) + (double-float-equal + (transform-float-1 (parse-integer (first (aref rows i)))) + (parse-double (second (aref rows i))))) + results)))) + ((t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t))) + + + (deftest :basic/map/2 + (with-dataset *ds-basic* + (let ((results '()) + (rows (map-query 'list #'identity "select * from TYPE_TABLE" + :result-types nil))) + (dotimes (i (length rows) results) + (push + (list + (listp (nth i rows)) + (length (nth i rows)) + (eql (- i 5) + (parse-integer (first (nth i rows)) + :junk-allowed nil)) + (double-float-equal + (transform-float-1 (parse-integer (first (nth i rows)))) + (parse-double (second (nth i rows))))) + results)))) + ((t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t))) + + (deftest :basic/map/3 + (with-dataset *ds-basic* + (let ((results '()) + (rows (map-query 'list #'identity "select * from TYPE_TABLE" + :result-types :auto))) + (dotimes (i (length rows) results) + (push + (list + (listp (nth i rows)) + (length (nth i rows)) + (eql (- i 5) + (first (nth i rows))) + (double-float-equal + (transform-float-1 (first (nth i rows))) + (second (nth i rows)))) + results)))) + ((t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t) (t 3 t t))) + + ;; confirm that a query on a single element returns a list of one element + (deftest :basic/map/4 + (with-dataset *ds-basic* + (let ((rows (map-query 'list #'identity "select t_int from TYPE_TABLE" + :result-types nil))) + (values + (consp (first rows)) + (length (first rows))))) + t 1) + + (deftest :basic/do/1 + (with-dataset *ds-basic* + (let ((results '())) + (do-query ((int float str) "select * from TYPE_TABLE" :result-types nil) + (let ((int-number (parse-integer int))) + (setq results + (cons (list (double-float-equal (transform-float-1 + int-number) + (parse-double float)) + (double-float-equal (parse-double str) + (parse-double float))) + results)))) + results)) + ((t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t))) + + (deftest :basic/do/2 + (with-dataset *ds-basic* + (let ((results '())) + (do-query ((int float str) "select * from TYPE_TABLE" :result-types :auto) + (setq results + (cons + (list (double-float-equal + (transform-float-1 int) + float) + (double-float-equal + (parse-double str) + float)) + results))) + results)) + ((t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t))) + + + (deftest :basic/bigint/1 + (with-dataset *ds-bigint* + (let ((results '())) + (dolist (row (query "select * from TYPE_BIGINT" :result-types :auto) + results) + (destructuring-bind (int bigint) row + (push (list (integerp int) + (integerp bigint)) + results))))) + ((t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t) (t t))) + + + (deftest :basic/bigtext/1 + (with-dataset *ds-bigtext* + (let* ((len 7499) + (str (make-string len :initial-element #\a)) + (cmd (format nil "INSERT INTO testbigtext (a) VALUES ('~a')" str))) + (execute-command cmd) + (let ((a (first (query "SELECT a from testbigtext" + :flatp t :field-names nil)))) + (assert (string= str a) (str a) + "mismatch on a. inserted: ~a returned: ~a" len (length a))) + )) + nil) + (deftest :basic/bigtext/2 + (flet ((random-char () + (let ((alphabet "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + (idx (random 52))) + (elt alphabet idx)))) + (dotimes (n 10) + (with-dataset *ds-bigtext* + (let* ((len (random 7500)) + (str (coerce (make-array len + :initial-contents (loop repeat len collect (random-char))) + 'string)) + (cmd (format nil "INSERT INTO testbigtext (a) VALUES ('~a')" str))) + (execute-command cmd) + (let ((a (first (query "SELECT a from testbigtext" + :flatp t :field-names nil)))) + (assert (string= str a) (str a) + "mismatch on randomized bigtext(~a) inserted: ~s returned: ~s" len str a)) + )))) + nil) + + (deftest :basic/reallybigintegers/1 + (with-dataset *ds-reallybigintegers* + (let* ((a (1- (expt 2 64))) + (b (- (expt 2 64) 2)) + (c (expt 2 63)) + (d (expt 2 62)) + (sql (format nil "INSERT INTO testreallybigintegers + VALUES (~A, ~A, ~A, ~A)" + a b c d))) + (query sql) + (let ((results + (query + (format nil "SELECT * FROM testreallybigintegers")))) + (equal `(,a ,b ,c ,d) (car results))))) + t) + )) + + +(def-dataset *ds-basic* + (:setup (lambda () + (ignore-errors + (clsql:execute-command "DROP TABLE TYPE_TABLE") + (clsql:execute-command "DROP TABLE TYPE_BIGINT")) + + (clsql:execute-command + "CREATE TABLE TYPE_TABLE (t_int integer, t_float double precision, t_str VARCHAR(30))") + + (dotimes (i 11) + (let* ((test-int (- i 5)) + (test-flt (transform-float-1 test-int))) + (clsql:execute-command + (format nil "INSERT INTO TYPE_TABLE VALUES (~a,~a,'~a')" + test-int + (clsql-sys:number-to-sql-string test-flt) + (clsql-sys:number-to-sql-string test-flt) + )))))) + (:cleanup "DROP TABLE TYPE_TABLE")) + +(def-dataset *ds-bigint* + (:setup (lambda () + (ignore-errors (clsql:execute-command "DROP TABLE TYPE_BIGINT")) + (clsql:execute-command "CREATE TABLE TYPE_BIGINT (T_INT integer, T_BIGINT BIGINT)") + (dotimes (i 11) + (clsql:execute-command + (format nil "INSERT INTO TYPE_BIGINT VALUES (~a,~a)" + (- i 5) + (transform-bigint-1 (- i 5))))))) + (:cleanup "DROP TABLE TYPE_BIGINT")) + +;;;; Testing functions + +(defun transform-float-1 (i) + (coerce (* i (abs (/ i 2)) (expt 10 (* 2 i))) 'double-float)) + +(defun transform-bigint-1 (i) + (* i (expt 10 (* 3 (abs i))))) + +(defun parse-double (num-str) + (let ((*read-default-float-format* 'double-float)) + (coerce (read-from-string num-str) 'double-float))) + +(defun double-float-equal (a b) + (if (zerop a) + (if (zerop b) + t + nil) + (let ((diff (abs (/ (- a b) a)))) + (if (> diff (* 10 double-float-epsilon)) + nil + t)))) + +(def-dataset *ds-bigtext* + (:setup "CREATE TABLE testbigtext(a varchar(7500))") + (:cleanup "DROP TABLE testbigtext")) + +(def-dataset *ds-reallybigintegers* + (:setup (lambda () + (ignore-errors + (clsql:execute-command "DROP TABLE testreallybigintegers")) + (clsql:execute-command + "CREATE TABLE testreallybigintegers( a BIGINT UNSIGNED, + b BIGINT UNSIGNED, + c BIGINT UNSIGNED, + d BIGINT UNSIGNED )"))) + (:cleanup "DROP TABLE testreallybigintegers")) diff --git a/tests/test-connection.lisp b/tests/test-connection.lisp new file mode 100644 index 0000000..3e742e7 --- /dev/null +++ b/tests/test-connection.lisp @@ -0,0 +1,80 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-connection.lisp +;;;; Purpose: Tests for CLSQL database connections +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + +(setq *rt-connection* + '( + +(deftest :connection/1 + (let ((database (clsql:find-database + (clsql:database-name clsql:*default-database*) + :db-type (clsql-sys:database-type clsql:*default-database*)))) + (eql (clsql-sys:database-type database) *test-database-type*)) + t) + +(deftest :connection/2 + (clsql-sys::string-to-list-connection-spec + "localhost/dbname/user/passwd") + ("localhost" "dbname" "user" "passwd")) + +(deftest :connection/3 + (clsql-sys::string-to-list-connection-spec + "dbname/user@hostname") + ("hostname" "dbname" "user")) + +(deftest :connection/execute-command + ;;check that we can issue basic commands. + (values + (clsql-sys:execute-command "CREATE TABLE DUMMY (foo integer)") + (clsql-sys:execute-command "DROP TABLE DUMMY")) + nil nil) + +(deftest :connection/query + ;;check that we can do a basic query + (first (clsql:query "SELECT 1" :flatp t :field-names nil)) + 1) + +(deftest :connection/query-command + ;;queries that are commands (no result set) shouldn't cause breakage + (values + (clsql-sys:query "CREATE TABLE DUMMY (foo integer)") + (clsql-sys:query "DROP TABLE DUMMY")) + nil nil) + +(deftest :connection/pool/procedure-mysql + (unwind-protect + (progn + (clsql-sys:disconnect) + (test-connect :pool t) + (clsql-sys:execute-command + "CREATE PROCEDURE prTest () BEGIN SELECT 1 \"a\",2 \"b\",3 \"c\" ,4 \"d\" UNION SELECT 5,6,7,8; END;") + (clsql-sys:disconnect) + (test-connect :pool t) + (let ((p0 (clsql-sys:query "CALL prTest();" :flatp t))) + (clsql-sys:disconnect) + (test-connect :pool t) + (let ((p1 (clsql-sys:query "CALL prTest();" :flatp t))) + (clsql-sys:disconnect) + (test-connect :pool t) + (values p0 p1)))) + (ignore-errors + (clsql-sys:execute-command "DROP PROCEDURE prTest;")) + (test-connect)) + ((1 2 3 4) (5 6 7 8)) + ((1 2 3 4) (5 6 7 8))) + +)) diff --git a/tests/test-fddl.lisp b/tests/test-fddl.lisp new file mode 100644 index 0000000..41e79d5 --- /dev/null +++ b/tests/test-fddl.lisp @@ -0,0 +1,454 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-fddl.lisp +;;;; Purpose: Tests for the CLSQL Functional Data Definition Language +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + + +(in-package #:clsql-tests) +(clsql-sys:file-enable-sql-reader-syntax) + +(def-dataset *ds-fddl* + (:setup (lambda () + (create-table [alpha] '(([a] integer) + ([c] (varchar 30)) + ([d] date) + ([f] float))) + (create-table [bravo] '(([foo] integer) + ([bar] integer))))) + (:sqldata "ALPHA" "A,C,D,F" + "1,'asdf','2010-01-01',3.14" + "2,'blarg','2012-12-21',0.1" + "3,'matey','1992-02-29',0.0") + (:cleanup "DROP TABLE ALPHA" "DROP TABLE BRAVO")) + +(def-dataset *ds-fddl-parsing-oddities* + (:setup "CREATE TABLE ATEST ( +A varchar (32), +B varchar(32))") + (:cleanup "DROP TABLE ATEST")) + +(setq *rt-fddl* + '( + +;; list current tables +(deftest :fddl/table/1 + (with-dataset *ds-fddl* + (let ((tables (sort (mapcar #'string-downcase (clsql:list-tables)) + #'string<))) + ;; sqlite has a table for autoincrement sequences that we dont care about if + ;; it exists + (remove "sqlite_sequence" tables :test #'string-equal))) + ("alpha" "bravo")) + +;; create a table, test for its existence, drop it and test again +(deftest :fddl/table/2 + (progn (clsql:create-table [foo] + '(([id] integer) + ([height] float) + ([name] (string 24)) + ([comments] longchar))) + (values + (clsql:table-exists-p [foo]) + (progn + (clsql:drop-table [foo] :if-does-not-exist :ignore) + (clsql:table-exists-p [foo])))) + t nil) + +;; create a table, list its attributes and drop it +(deftest :fddl/table/3 + (apply #'values + (progn (clsql:create-table [foo] + '(([id] integer) + ([height] float) + ([name] (char 255)) + ([comments] longchar))) + (prog1 + (sort (mapcar #'string-downcase + (clsql:list-attributes [foo])) + #'string<) + (clsql:drop-table [foo] :if-does-not-exist :ignore)))) + "comments" "height" "id" "name") + +(deftest :fddl/table/4 + (values + (clsql:table-exists-p "MyMixedCase") + (progn + (clsql:create-table "MyMixedCase" '(([a] integer))) + (clsql:table-exists-p "MyMixedCase")) + (progn + (clsql:drop-table "MyMixedCase") + (clsql:table-exists-p "MyMixedCase"))) + nil t nil) + +(deftest :fddl/table/5 + (prog1 + (progn + (clsql:create-table "MyMixedCase" '(([a] integer))) + (clsql:execute-command "insert into \"MyMixedCase\" values (5)") + (clsql:insert-records :into "MyMixedCase" :values '(6)) + (clsql:select [a] :from "MyMixedCase" :order-by '((a :asc)))) + (clsql:drop-table "MyMixedCase")) + ((5) (6))) + +(deftest :fddl/table/6 + (values + (clsql:table-exists-p [foo]) + (progn + (let ((*backend-warning-behavior* + (if (member *test-database-type* + '(:postgresql :postgresql-socket :postgresql-socket3)) + :ignore + :warn))) + (case *test-database-underlying-type* + (:mssql (clsql:create-table [foo] + '(([bar] integer :not-null :primary-key) + ([baz] string :not-null :unique)))) + (t (clsql:create-table [foo] + '(([bar] integer :not-null :unique :primary-key) + ([baz] string :not-null :unique)))))) + (clsql:table-exists-p [foo])) + (progn + (clsql:drop-table [foo]) + (clsql:table-exists-p [foo]))) + nil t nil) + +(deftest :fddl/table/7 + (values + (clsql:table-exists-p [foo]) + (progn + (let ((*backend-warning-behavior* + (if (member *test-database-type* + '(:postgresql :postgresql-socket :postgresql-socket3)) + :ignore + :warn))) + (clsql:create-table [foo] '(([bar] integer :not-null) + ([baz] string :not-null)) + :constraints '("UNIQUE (bar,baz)" + "PRIMARY KEY (bar)"))) + (clsql:table-exists-p [foo])) + (progn + (clsql:drop-table [foo]) + (clsql:table-exists-p [foo]))) + nil t nil) + +(deftest :fddl/attributes/1 + (apply #'values + (with-dataset *ds-fddl* + (sort + (mapcar #'string-downcase + (clsql:list-attributes [alpha] )) + #'string<))) + "a" "c" "d" "f") + +(deftest :fddl/attributes/2 + (with-dataset *ds-fddl* + (apply #'values + (sort + (mapcar #'(lambda (a) (string-downcase (car a))) + (clsql:list-attribute-types [alpha])) + #'string<))) + "a" "c" "d" "f") + +;; Attribute types are vendor specific so need to test a range +(deftest :fddl/attributes/3 + (with-dataset *ds-fddl* + (and (member (clsql:attribute-type [a] [alpha]) '(:int :integer :int4 :number)) t)) + t) + +(deftest :fddl/attributes/4 + (with-dataset *ds-fddl* + (multiple-value-bind (type length scale nullable) + (clsql:attribute-type [c] [alpha]) + (values (clsql-sys:in type :varchar :varchar2 :nvarchar) + length scale nullable))) + t 30 nil 1) + +(deftest :fddl/attributes/5 + (with-dataset *ds-fddl* + (and (member (clsql:attribute-type [d] [alpha]) + '(:datetime :timestamp :date :smalldatetime)) t)) + t) + +(deftest :fddl/attributes/6 + (with-dataset *ds-fddl* + (and (member (clsql:attribute-type [f] [alpha]) '(:float :float8 :number)) t)) + t) + +(deftest :fddl/attributes/7 + (with-dataset *ds-bigint* + (and (member (clsql:attribute-type [t_bigint] [TYPE_BIGINT]) '(:bigint :int8)) t)) + t) + +(deftest :fddl/attributes/8 + ;;this is mostly from sqlite3 sending back + (with-dataset *ds-fddl-parsing-oddities* + (values + (clsql-sys:in (clsql:attribute-type [a] [atest]) :varchar :varchar2) + (clsql-sys:in (clsql:attribute-type [b] [atest]) :varchar :varchar2))) + t t) + + +;; create a view, test for existence, drop it and test again +(deftest :fddl/view/1 + (with-dataset *ds-fddl* + (progn (clsql:create-view [v1] + :as [select [a] [c] [d] + :from [alpha] + :where [= [a] 1]]) + (values + (clsql:view-exists-p [v1]) + (progn + (clsql:drop-view [v1] :if-does-not-exist :ignore) + (clsql:view-exists-p [v1]))))) + t nil) + + ;; create a view, list its attributes and drop it +(deftest :fddl/view/2 + (with-dataset *ds-fddl* + (progn (clsql:create-view [v1] + :as [select [a] [c] [d] + :from [alpha] + :where [= [a] 1]]) + (unwind-protect + (sort (mapcar #'string-downcase + (clsql:list-attributes [v1])) + #'string<) + (clsql:drop-view [v1] :if-does-not-exist :ignore)))) + ("a" "c" "d")) + + ;; create a view, select stuff from it and drop it +(deftest :fddl/view/3 + (with-dataset *ds-fddl* + (progn + (clsql:create-view [v1] + :as [select [a] [c] [d] + :from [alpha] + :where [= [a] 1]]) + (unwind-protect + (let ((result + (list + ;; Shouldn't exist + (clsql:select [a] [c] + :from [v1] + :where [= [a] -1]) + ;; Should exist + (car (clsql:select [a] [c] + :from [v1] + :where [= [a] 1]))))) + + (apply #'values result)) + (clsql:drop-view [v1] :if-does-not-exist :ignore)))) + nil (1 "asdf")) + +(deftest :fddl/view/4 + (with-dataset *ds-fddl* + (progn + (clsql:create-view [v1] + :column-list '([x] [y] [z]) + :as [select [a] [c] [d] + :from [alpha] + :where [= [a] 1]]) + (unwind-protect + (let ((result + (list + (sort (mapcar #'string-downcase + (clsql:list-attributes [v1])) + #'string<) + ;; Shouldn't exist + (clsql:select [x] [y] + :from [v1] + :where [= [x] -1]) + ;; Should exist + (car (clsql:select [x] [y] + :from [v1] + :where [= [x] 1]))))) + + (apply #'values result)) + (clsql:drop-view [v1] :if-does-not-exist :ignore)))) + ("x" "y" "z") nil (1 "asdf")) + +;; create an index, test for existence, drop it and test again +(deftest :fddl/index/1 + (with-dataset *ds-fddl* + (progn (clsql:create-index [bar] :on [alpha] :attributes + '([a] [c]) :unique t) + (values + (clsql:index-exists-p [bar] ) + (progn + (clsql:drop-index [bar] :on [alpha] + :if-does-not-exist :ignore) + (clsql:index-exists-p [bar]))))) + t nil) + +;; create indexes with names as strings, symbols and in square brackets +(deftest :fddl/index/2 + (with-dataset *ds-fddl* + (let ((names '("foo" foo [foo])) + (result '())) + (dolist (name names) + (clsql:create-index name :on [alpha] :attributes '([a])) + (push (clsql:index-exists-p name ) result) + (clsql:drop-index name :on [alpha] :if-does-not-exist :ignore)) + (apply #'values result))) + t t t) + +;; test list-indexes with keyword :ON +(deftest :fddl/index/3 + (progn + (clsql:create-table [i3test] '(([a] (string 10)) + ([b] integer))) + (clsql:create-index [foo] :on [i3test] :attributes + '([b]) :unique nil) + (clsql:create-index [bar] :on [i3test] :attributes + '([a]) :unique t) + (values + (clsql:table-exists-p [i3test]) + (clsql:index-exists-p [foo]) + (clsql:index-exists-p [bar]) + (sort + (mapcar + #'string-downcase + (clsql:list-indexes :on [i3test])) + #'string-lessp) + (progn + (clsql:drop-index [bar] :on [i3test]) + (clsql:drop-index [foo] :on [i3test]) + (clsql:drop-table [i3test]) + t))) + t t t ("bar" "foo") t) + +;; create an sequence, test for existence, drop it and test again +(deftest :fddl/sequence/1 + (progn (clsql:create-sequence [foo]) + (values + (clsql:sequence-exists-p [foo]) + (progn + (clsql:drop-sequence [foo] :if-does-not-exist :ignore) + (clsql:sequence-exists-p [foo])))) + t nil) + +;; create and increment a sequence +(deftest :fddl/sequence/2 + (let ((val1 nil)) + (clsql:create-sequence [foo]) + (setf val1 (clsql:sequence-next [foo])) + (prog1 + (< val1 (clsql:sequence-next [foo])) + (clsql:drop-sequence [foo] :if-does-not-exist :ignore))) + t) + +;; explicitly set the value of a sequence +(deftest :fddl/sequence/3 + (progn + (clsql:create-sequence [foo]) + (clsql:set-sequence-position [foo] 5) + (prog1 + (clsql:sequence-next [foo]) + (clsql:drop-sequence [foo] :if-does-not-exist :ignore))) + 6) + + + +(deftest :fddl/owner/1 + (with-dataset *ds-fddl* + (and + ;; user tables are an improper subset of all tables + (= (length (intersection (clsql:list-tables :owner nil) + (clsql:list-tables :owner :all) + :test #'string=)) + (length (clsql:list-tables :owner nil))) + ;; user tables are a proper subset of all tables + (> (length (clsql:list-tables :owner :all)) + (length (clsql:list-tables :owner nil))))) + t) + +(deftest :fddl/owner/table + (with-dataset *ds-fddl* + (values + (clsql-sys:table-exists-p [alpha]) + (clsql-sys:table-exists-p [alpha] :owner *test-database-user*) + (clsql-sys:table-exists-p [alpha] :owner *test-false-database-user*))) + t t nil) + +(deftest :fddl/owner/attributes + (with-dataset *ds-fddl* + (values + (length (clsql-sys:list-attributes [alpha])) + (length (clsql-sys:list-attributes [alpha] :owner *test-database-user*)) + (length (clsql-sys:list-attributes [alpha] :owner *test-false-database-user*)))) + 4 4 0) + +(deftest :fddl/owner/attribute-types + (with-dataset *ds-fddl* + (values + (length (clsql:list-attribute-types [alpha])) + (length (clsql:list-attribute-types [alpha] :owner *test-database-user*)) + (length (clsql:list-attribute-types [alpha] :owner *test-false-database-user*)))) + 4 4 0) + +(deftest :fddl/owner/index + (with-dataset *ds-fddl* + (progn (clsql:create-index [bar] :on [alpha] + :attributes '([a] [c])) + (values + (clsql:index-exists-p [bar] ) + (clsql:index-exists-p [bar] :owner *test-database-user*) + (clsql:index-exists-p [bar] :owner *test-false-database-user*) + + (length (clsql-sys:list-indexes :on [alpha])) + (length (clsql-sys:list-indexes :on [alpha] :owner *test-database-user*)) + (length (clsql-sys:list-indexes :on [alpha] :owner *test-false-database-user*)) + (progn + (clsql:drop-index [bar] :on [alpha] + :if-does-not-exist :ignore) + (clsql:index-exists-p [bar] :owner *test-database-user*)) + (clsql:index-exists-p [bar] )))) + t t nil + 1 1 0 + nil nil) + +(deftest :fddl/owner/sequence + (progn (clsql:create-sequence [foo]) + (values + (clsql:sequence-exists-p [foo]) + (clsql:sequence-exists-p [foo] :owner *test-database-user*) + (clsql:sequence-exists-p [foo] :owner *test-false-database-user*) + + (progn + (clsql:drop-sequence [foo] :if-does-not-exist :ignore) + (clsql:sequence-exists-p [foo] )))) + t t nil nil) + + + +(deftest :fddl/cache-table-queries/1 + (with-dataset *ds-fddl* + (list + (gethash "ALPHA" (clsql-sys::attribute-cache clsql:*default-database*)) + (progn + (clsql:cache-table-queries "ALPHA" :action t) + (gethash "ALPHA" (clsql-sys::attribute-cache clsql:*default-database*))) + (progn + (clsql:list-attribute-types "ALPHA") + (not + (null + (cadr + (gethash "ALPHA" + (clsql-sys::attribute-cache clsql:*default-database*)))))) + (progn + (clsql:cache-table-queries "ALPHA" :action :flush) + (gethash "ALPHA" (clsql-sys::attribute-cache clsql:*default-database*))))) + (nil (t nil) t (t nil))) + + )) diff --git a/tests/test-fdml.lisp b/tests/test-fdml.lisp new file mode 100644 index 0000000..51cdd02 --- /dev/null +++ b/tests/test-fdml.lisp @@ -0,0 +1,780 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ====================================================================== +;;;; File: test-fdml.lisp +;;;; Author: Marcus Pearce , Kevin Rosenberg +;;;; Created: 30/03/2004 +;;;; +;;;; Tests for the CLSQL Functional Data Manipulation Language +;;;; (FDML). +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ====================================================================== + +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) +;;started defining an independent dataset that doesn't depend on the view-classes +;; but there is a *lot* of stuff in the file assuming that dataset. +;; (def-dataset *ds-fdml* +;; (:setup (lambda () +;; (let ((*backend-warning-behavior* +;; (if (member *test-database-type* '(:postgresql :postgresql-socket)) +;; :ignore +;; :warn))) +;; (clsql-sys:execute-command "CREATE TABLE EMPLOYEE ( +;; emplid integer NOT NULL, +;; groupid integer NOT NULL, +;; first_name character varying(30), +;; last_name character varying(30), +;; email character varying(100), +;; ecompanyid integer, +;; managerid integer, +;; height double precision, +;; married boolean, +;; birthday timestamp, +;; bd_utime bigint, +;; CONSTRAINT employeepk PRIMARY KEY (emplid, groupid), +;; CONSTRAINT employee_emplid_key UNIQUE (emplid) +;; )")))) +;; (:sqldata "EMPLOYEE" +;; "emplid,groupid,first_name,last_name,email,height,birthday" +;; "10,1,'a','b','a@b.org',1.9,current_timestamp" +;; "11,1,'x','y','x@y.org',null,current_timestamp" +;; ) +;; (:cleanup "DROP TABLE EMPLOYEE") +;; ) + +(setq *rt-fdml* + '( + +;; Computed values are not always classified as numeric by psqlodbc +(deftest :fdml/query/1 + (with-dataset *ds-employees* + (let ((count (caar (clsql:query "SELECT COUNT(*) FROM EMPLOYEE WHERE (EMAIL LIKE '%org')" :field-names nil)))) + (%get-int count))) + 10) + +(deftest :fdml/query/2 + (with-dataset *ds-employees* + (multiple-value-bind (rows field-names) + (clsql:query + "SELECT FIRST_NAME,LAST_NAME FROM EMPLOYEE WHERE (EMPLID <= 5) ORDER BY LAST_NAME") + (values rows (mapcar 'string-upcase field-names)))) + (("Leonid" "Brezhnev") ("Nikita" "Kruschev") ("Vladimir" "Lenin") + ("Josef" "Stalin") ("Leon" "Trotsky")) + ("FIRST_NAME" "LAST_NAME")) + +(deftest :fdml/query/3 + (with-dataset *ds-employees* + (caar (clsql:query "SELECT EMPLID FROM EMPLOYEE WHERE LAST_NAME = 'Andropov'" :field-names nil))) + 6) + +(deftest :fdml/query/4 + (with-dataset *ds-employees* + (typep (caar (clsql:query "SELECT HEIGHT FROM EMPLOYEE WHERE LAST_NAME = 'Andropov'" :field-names nil)) + 'float)) + t) + +(deftest :fdml/query/5 + (with-dataset *ds-employees* + (let ((res (clsql:query (clsql:sql [select [first-name] [sum [emplid]] :from [employee]] + [group-by [first-name]] [order-by [sum [emplid]]]) + :field-names nil :result-types nil))) + (mapcar (lambda (p) (list (car p) (%get-int (second p)))) + res))) + (("Josef" 2) ("Leon" 3) ("Nikita" 4) ("Leonid" 5) ("Yuri" 6) + ("Konstantin" 7) ("Mikhail" 8) ("Boris" 9) ("Vladimir" 11))) + +(deftest :fdml/query/6 + (with-dataset *ds-employees* + (let ((res (clsql:query (clsql:sql [union [select [emplid] :from [employee]] + [select [groupid] :from [company]]]) + :field-names nil :result-types nil :flatp t + ))) + (values (or (eql *test-database-type* :postgresql-socket3) + (every #'stringp res)) + (sort (mapcar #'%get-int res) + #'<=)))) + t (1 2 3 4 5 6 7 8 9 10)) + +(deftest :fdml/query/7 + (with-dataset *ds-employees* + (let ((res (car (clsql:query (clsql:sql [intersect [select [emplid] :from [employee]] + [select [groupid] :from [company]]]) + :field-names nil :result-types nil :flatp t)))) + (values (or (stringp res) + (eql *test-database-type* :postgresql-socket3)) + (nth-value 0 (%get-int res))))) + t 1) + +(deftest :fdml/query/8 + (with-dataset *ds-employees* + (let ((res (clsql:query (clsql:sql [except [select [emplid] :from [employee]] + [select [groupid] :from [company]]]) + :field-names nil :result-types nil :flatp t))) + (values (or (every #'stringp res) + (eql *test-database-type* :postgresql-socket3)) + (sort (mapcar #'%get-int res) + #'<=)))) + t (2 3 4 5 6 7 8 9 10)) + +;; compare min, max and average hieghts in inches (they're quite short +;; these guys!) +(deftest :fdml/select/1 + (with-dataset *ds-employees* + (let ((max (clsql:select [function "floor" + [/ [* [max [height]] 100] 2.54]] + :from [employee] + :result-types nil + :flatp t)) + (min (clsql:select [function "floor" + [/ [* [min [height]] 100] 2.54]] + :from [employee] + :result-types nil + :flatp t)) + (avg (clsql:select [function "floor" + [avg [/ [* [height] 100] 2.54]]] + :from [employee] + :result-types nil + :flatp t))) + (apply #'< (mapcar #'%get-int (append min avg max))))) + t) + +(deftest :fdml/select/2 + (with-dataset *ds-employees* + (clsql:select [first-name] :from [employee] :flatp t :distinct t + :field-names nil + :result-types nil + :order-by [first-name])) + ("Boris" "Josef" "Konstantin" "Leon" "Leonid" "Mikhail" "Nikita" "Vladimir" + "Yuri")) + +(deftest :fdml/select/3 + (with-dataset *ds-employees* + (let ((res (clsql:select [first-name] [count [*]] :from [employee] + :result-types nil + :group-by [first-name] + :order-by [first-name] + :field-names nil))) + (mapcar (lambda (p) (list (car p) (%get-int (second p)))) + res))) + (("Boris" 1) ("Josef" 1) ("Konstantin" 1) ("Leon" 1) ("Leonid" 1) + ("Mikhail" 1) ("Nikita" 1) ("Vladimir" 2) ("Yuri" 1))) + +(deftest :fdml/select/4 + (with-dataset *ds-employees* + (clsql:select [last-name] :from [employee] + :where [like [email] "%org"] + :order-by [last-name] + :field-names nil + :result-types nil + :flatp t)) + ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin")) + +(deftest :fdml/select/5 + (with-dataset *ds-employees* + (clsql:select [email] :from [employee] :flatp t :result-types nil + :where [in [employee emplid] + [select [managerid] :from [employee]]] + :field-names nil)) + ("lenin@soviet.org")) + +(deftest :fdml/select/6 + (with-dataset *ds-employees* + (if (clsql-sys:db-type-has-fancy-math? *test-database-underlying-type*) + (mapcar #'%get-int + (clsql:select [function "trunc" [height]] :from [employee] + :result-types nil + :field-names nil + :flatp t)) + (mapcar #'%get-int + (clsql:select [height] :from [employee] :flatp t + :field-names nil :result-types nil)))) + (1 1 1 1 1 1 1 1 1 1)) + +(deftest :fdml/select/7 + (with-dataset *ds-employees* + (let ((result (car (clsql:select [max [emplid]] :from [employee] :flatp t + :field-names nil :result-types nil)))) + (values + (nth-value 0 (%get-int result))))) + 10) + +(deftest :fdml/select/8 + (with-dataset *ds-employees* + (let ((result (car (clsql:select [min [emplid]] :from [employee] :flatp t + :field-names nil :result-types nil)))) + (values + (nth-value 0 (%get-int result))))) + 1) + +(deftest :fdml/select/9 + (with-dataset *ds-employees* + (let ((val (car (clsql:select + [avg [emplid]] :from [employee] :flatp t + :field-names nil :result-types nil)))) + (typecase val + (string (subseq val 0 3)) + (number (format nil "~,1F" val))))) + "5.5") + +(deftest :fdml/select/10 + (with-dataset *ds-employees* + (clsql:select [last-name] :from [employee] + :where [not [in [emplid] + [select [managerid] :from [company]]]] + :result-types nil + :field-names nil + :flatp t + :order-by [last-name])) + ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Putin" "Stalin" + "Trotsky" "Yeltsin")) + +(deftest :fdml/select/11 + (with-dataset *ds-employees* + (clsql:select [last-name] :from [employee] :where [married] :flatp t + :field-names nil :order-by [emplid] :result-types nil)) + ("Lenin" "Stalin" "Trotsky")) + +(deftest :fdml/select/12 + (with-dataset *ds-employees* + (let ((v 1)) + (clsql:select [last-name] :from [employee] :where [= [emplid] v] + :field-names nil :result-types nil))) + (("Lenin"))) + +(deftest :fdml/select/13 + (with-dataset *ds-employees* + (multiple-value-bind (results field-names) + (clsql:select [emplid] [last-name] :from [employee] + :where [= [emplid] 1]) + (values results (mapcar #'string-downcase field-names)))) + ((1 "Lenin")) + ("emplid" "last_name")) + +(deftest :fdml/select/14 + (with-dataset *ds-employees* + (floatp (car (clsql:select [height] :from [employee] :where [= [emplid] 1] + :flatp t)))) + t) + +(deftest :fdml/select/15 + (with-dataset *ds-employees* + (multiple-value-bind (rows field-names) + (clsql:select [addressid] [street-number] [street-name] [city_field] [zip] + :from [addr] + :where [= 1 [addressid]]) + (values + rows + (mapcar #'string-downcase field-names)))) + ((1 10 "Park Place" "Leningrad" 123)) + ("addressid" "street_number" "street_name" "city_field" "zip")) + +(deftest :fdml/select/16 + (with-dataset *ds-employees* + (clsql:select [emplid] :from [employee] :where [= 1 [emplid]] + :field-names nil)) + ((1))) + +(deftest :fdml/select/17 + (with-dataset *ds-employees* + (clsql:select [emplid] [last-name] :from [employee] :where [= 1 [emplid]] + :field-names nil)) + ((1 "Lenin"))) + +(deftest :fdml/select/18 + (with-dataset *ds-employees* + (clsql:select [emplid :string] [last-name] :from [employee] :where [= 1 [emplid]] + :field-names nil)) + (("1" "Lenin"))) + +(deftest :fdml/select/19 + (with-dataset *ds-employees* + (mapcar + #'%get-int + (clsql:select [emplid] :from [employee] :order-by [emplid] + :where [between [* [emplid] 10] [* 5 10] [* 10 10]] + :field-names nil :result-types nil :flatp t))) + (5 6 7 8 9 10)) + +(deftest :fdml/select/20 + (with-dataset *ds-employees* + (mapcar #'%get-int + (clsql:select [emplid] :from [employee] :order-by [emplid] + :where [not [between [* [emplid] 10] [* 5 10] [* 10 10]]] + :field-names nil :result-types nil :flatp t))) + (1 2 3 4)) + +(deftest :fdml/select/21 + (with-dataset *ds-employees* + (clsql:select [substring [first-name] 1 4] :from [employee] + :flatp t :order-by [emplid] :field-names nil)) + ("Vlad" "Jose" "Leon" "Niki" "Leon" "Yuri" "Kons" "Mikh" "Bori" "Vlad")) + +(deftest :fdml/select/22 + (with-dataset *ds-employees* + (case *test-database-underlying-type* + (:mssql (clsql:select [+ [first-name] " " [last-name]] :from [employee] + :flatp t :order-by [emplid] :field-names nil)) + (t (clsql:select [|| [first-name] " " [last-name]] :from [employee] + :flatp t :order-by [emplid] :field-names nil)))) + ("Vladimir Lenin" "Josef Stalin" "Leon Trotsky" "Nikita Kruschev" + "Leonid Brezhnev" "Yuri Andropov" "Konstantin Chernenko" "Mikhail Gorbachev" + "Boris Yeltsin" "Vladimir Putin")) + +(deftest :fdml/select/23 + (with-dataset *ds-employees* + (mapcar #'%get-int + (clsql:select [emplid] :from [employee] :where [in [emplid] '(1 2 3 4)] + :flatp t :order-by [emplid] :field-names nil + :result-types nil))) + (1 2 3 4)) + +(deftest :fdml/select/24 + (with-dataset *ds-employees* + (clsql:select [distinct [first-name]] :from [employee] :flatp t + :order-by [first-name] :field-names nil :result-types nil)) + ("Boris" "Josef" "Konstantin" "Leon" "Leonid" "Mikhail" "Nikita" "Vladimir" + "Yuri")) + +(deftest :fdml/select/25 + (with-dataset *ds-employees* + (clsql:select [first-name] :from (clsql-sys:convert-to-db-default-case "employee" *default-database*) + :flatp t :distinct t + :field-names nil + :result-types nil + :order-by [first-name])) + ("Boris" "Josef" "Konstantin" "Leon" "Leonid" "Mikhail" "Nikita" "Vladimir" + "Yuri")) + +(deftest :fdml/select/26 + (with-dataset *ds-employees* + (clsql:select ["table" first-name] ["table" last-name] + :from '([employee "table"] [employee "join"]) + :where [and [= ["table" first-name] + ["join" first-name]] + [not [= ["table" emplid] + ["join" emplid]]]] + :order-by '(["table" last-name]) + :result-types nil :field-names nil)) + (("Vladimir" "Lenin") ("Vladimir" "Putin"))) + +(deftest :fdml/select/27 + (with-dataset *ds-employees* + (mapcar + #'%get-int + (clsql:select [coalesce [managerid] 10] :from [employee] :order-by [emplid] + :field-names nil :result-types nil :flatp t))) + (10 1 1 1 1 1 1 1 1 1)) + +(deftest :fdml/select/28 + (with-dataset *ds-employees* + (loop for column in `([*] [emplid]) + collect + (%get-int + (car + (clsql:select [count column] :from [employee] + :flatp t :result-types nil :field-names nil))))) + (10 10)) + +(deftest :fdml/select/29 + (with-dataset *ds-employees* + (clsql:select [first-name] [last-name] :from [employee] + :result-types nil :field-names nil + :order-by '(([first-name] :asc) ([last-name] :desc)))) + (("Boris" "Yeltsin") ("Josef" "Stalin") ("Konstantin" "Chernenko") + ("Leon" "Trotsky") ("Leonid" "Brezhnev") ("Mikhail" "Gorbachev") + ("Nikita" "Kruschev") ("Vladimir" "Putin") ("Vladimir" "Lenin") + ("Yuri" "Andropov"))) + +(deftest :fdml/select/30 + (with-dataset *ds-employees* + (clsql:select [first-name] [last-name] :from [employee] + :result-types nil :field-names nil + :order-by '(([first-name] :asc) ([last-name] :asc)))) + (("Boris" "Yeltsin") ("Josef" "Stalin") ("Konstantin" "Chernenko") + ("Leon" "Trotsky") ("Leonid" "Brezhnev") ("Mikhail" "Gorbachev") + ("Nikita" "Kruschev") ("Vladimir" "Lenin") ("Vladimir" "Putin") + ("Yuri" "Andropov"))) + +(deftest :fdml/select/31 + (with-dataset *ds-employees* + (clsql:select [last-name] :from [employee] + :set-operation [union [select [first-name] :from [employee] + :order-by [last-name]]] + :flatp t + :result-types nil + :field-names nil)) + ("Andropov" "Boris" "Brezhnev" "Chernenko" "Gorbachev" "Josef" "Konstantin" + "Kruschev" "Lenin" "Leon" "Leonid" "Mikhail" "Nikita" "Putin" "Stalin" + "Trotsky" "Vladimir" "Yeltsin" "Yuri")) + +(deftest :fdml/select/32 + (with-dataset *ds-employees* + (mapcar + #'%get-int + (clsql:select [emplid] :from [employee] + :where [= [emplid] [any [select [companyid] :from [company]]]] + :flatp t :result-types nil :field-names nil))) + (1)) + +(deftest :fdml/select/33 + (with-dataset *ds-employees* + (clsql:select [last-name] :from [employee] + :where [> [emplid] [all [select [groupid] :from [employee]]]] + :order-by [last-name] + :flatp t :result-types nil :field-names nil)) + ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Putin" "Stalin" + "Trotsky" "Yeltsin")) + +(deftest :fdml/select/34 + (with-dataset *ds-employees* + (loop for x from 1 below 5 + collect + (car + (clsql:select [last-name] :from [employee] + :where [= [emplid] x] + :flatp t :result-types nil :field-names nil)))) + ("Lenin" "Stalin" "Trotsky" "Kruschev")) + +;; test escaping of single quotes +(deftest :fdml/select/35 + (with-dataset *ds-fddl* + (first (clsql:select "What's up doc?" :from [alpha] :flatp t :field-names nil))) + "What's up doc?") + +;; test proper treatment of backslash (depending on backend) +(deftest :fdml/select/36 + (with-dataset *ds-fddl* + (first (clsql:select "foo\\bar\\baz" :from [alpha] :flatp t :field-names nil))) + "foo\\bar\\baz") + +(deftest :fdml/select/37 + (with-dataset *ds-employees* + (clsql:select [emplid] :from [employee] + :order-by [emplid] + :limit 5 + :field-names nil + :flatp t)) + (1 2 3 4 5)) + +(deftest :fdml/select/38 + (with-dataset *ds-employees* + (clsql:select [emplid] :from [employee] + :order-by [emplid] + :limit 5 + :offset 3 + :field-names nil + :flatp t)) + (4 5 6 7 8)) + +(deftest :fdml/do-query/1 + (with-dataset *ds-employees* + (let ((result '())) + (clsql:do-query ((name) [select [last-name] :from [employee] + :order-by [last-name]]) + (push name result)) + result)) + ("Yeltsin" "Trotsky" "Stalin" "Putin" "Lenin" "Kruschev" "Gorbachev" + "Chernenko" "Brezhnev" "Andropov")) + +(deftest :fdml/map-query/1 + (with-dataset *ds-employees* + (clsql:map-query 'list #'identity + [select [last-name] :from [employee] :flatp t + :order-by [last-name]])) + ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin")) + +(deftest :fdml/map-query/2 + (with-dataset *ds-employees* + (clsql:map-query 'vector #'identity + [select [last-name] :from [employee] :flatp t + :order-by [last-name]])) + #("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin")) + +(deftest :fdml/map-query/3 + (with-dataset *ds-employees* + (clsql:map-query 'list #'identity + [select [last-name] :from [employee] :order-by [last-name]])) + (("Andropov") ("Brezhnev") ("Chernenko") ("Gorbachev") ("Kruschev") ("Lenin") + ("Putin") ("Stalin") ("Trotsky") ("Yeltsin"))) + +(deftest :fdml/map-query/4 + (with-dataset *ds-employees* + (clsql:map-query 'list #'identity + [select [first-name] [last-name] :from [employee] + :order-by [last-name]])) + (("Yuri" "Andropov") ("Leonid" "Brezhnev") ("Konstantin" "Chernenko") + ("Mikhail" "Gorbachev") ("Nikita" "Kruschev") ("Vladimir" "Lenin") + ("Vladimir" "Putin") ("Josef" "Stalin") ("Leon" "Trotsky") + ("Boris" "Yeltsin"))) + +(deftest :fdml/loop/1 + (with-dataset *ds-employees* + (loop for (forename surname) + being each tuple in + [select [first-name] [last-name] :from [employee] :order-by [last-name]] + collect (concatenate 'string forename " " surname))) + ("Yuri Andropov" "Leonid Brezhnev" "Konstantin Chernenko" "Mikhail Gorbachev" + "Nikita Kruschev" "Vladimir Lenin" "Vladimir Putin" + "Josef Stalin" "Leon Trotsky" "Boris Yeltsin")) + +(deftest :fdml/loop/2 + (with-dataset *ds-employees* + (loop for (addressid) + being each tuple in + [select [addressid] :from [addr] :order-by [addressid]] + collect addressid)) + (1 2 3)) + +(deftest :fdml/loop/3 + (with-dataset *ds-employees* + (loop for addressid + being each tuple in + [select [addressid] :from [addr] :order-by [addressid]] + collect addressid)) + (1 2 3)) + +;; inserts a record using all values only and then deletes it +(deftest :fdml/insert/1 + (with-dataset *ds-employees* + (let ((now (get-universal-time))) + (clsql:insert-records + :into [employee] + :values `(11 1 "clsql-sys::astronaut" "Yuri" "Gagarin" "gagarin@soviet.org" + 1 1 1.85 t ,(clsql:utime->time now) ,now)) + (values + (clsql:select [first-name] [last-name] [email] + :from [employee] :where [= [emplid] 11]) + (progn (clsql:delete-records :from [employee] :where [= [emplid] 11]) + (clsql:select [*] :from [employee] :where [= [emplid] 11]))))) + (("Yuri" "Gagarin" "gagarin@soviet.org")) nil) + +;; inserts a record using attributes and values and then deletes it +(deftest :fdml/insert/2 + (with-dataset *ds-employees* + (progn + (clsql:insert-records :into [employee] + :attributes '(emplid groupid first_name last_name + email ecompanyid managerid) + :values '(11 1 "Yuri" "Gagarin" "gagarin@soviet.org" + 1 1)) + (values + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 11]) + (progn (clsql:delete-records :from [employee] :where [= [emplid] 11]) + (clsql:select [*] :from [employee] :where [= [emplid] 11]))))) + (("Yuri" "Gagarin" "gagarin@soviet.org")) nil) + +;; inserts a record using av-pairs and then deletes it +(deftest :fdml/insert/3 + (with-dataset *ds-employees* + (progn + (clsql:insert-records :into [employee] + :av-pairs'((emplid 11) (groupid 1) + (first_name "Yuri") + (last_name "Gagarin") + (email "gagarin@soviet.org") + (ecompanyid 1) (managerid 1))) + (values + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 11]) + (progn (clsql:delete-records :from [employee] :where [= [emplid] 11]) + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 11]))))) + (("Yuri" "Gagarin" "gagarin@soviet.org")) nil) + +;; inserts a records using a query from another table +(deftest :fdml/insert/4 + (with-dataset *ds-employees* + (progn + (clsql:create-table [employee2] '(([forename] string) + ([surname] string) + ([email] string))) + (clsql:insert-records :into [employee2] + :query [select [first-name] [last-name] [email] + :from [employee]] + :attributes '(forename surname email)) + (prog1 + (equal (clsql:select [*] :from [employee2]) + (clsql:select [first-name] [last-name] [email] + :from [employee])) + (clsql:drop-table [employee2] :if-does-not-exist :ignore)))) + t) + +;; updates a record using attributes and values and then deletes it +(deftest :fdml/update/1 + (with-dataset *ds-employees* + (progn + (clsql:update-records [employee] + :attributes '(first_name last_name email) + :values '("Yuri" "Gagarin" "gagarin@soviet.org") + :where [= [emplid] 1]) + (values + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 1]) + (progn + (clsql:update-records [employee] + :av-pairs'((first_name "Vladimir") + (last_name "Lenin") + (email "lenin@soviet.org")) + :where [= [emplid] 1]) + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 1]))))) + (("Yuri" "Gagarin" "gagarin@soviet.org")) + (("Vladimir" "Lenin" "lenin@soviet.org"))) + +;; updates a record using av-pairs and then deletes it +(deftest :fdml/update/2 + (with-dataset *ds-employees* + (progn + (clsql:update-records [employee] + :av-pairs'((first_name "Yuri") + (last_name "Gagarin") + (email "gagarin@soviet.org")) + :where [= [emplid] 1]) + (values + (clsql:select [first-name] [last-name] [email] :from [employee] + :where [= [emplid] 1]) + (progn + (clsql:update-records [employee] + :av-pairs'((first_name "Vladimir") + (last_name "Lenin") + (email "lenin@soviet.org")) + :where [= [emplid] 1]) + (clsql:select [first-name] [last-name] [email] + :from [employee] :where [= [emplid] 1]))))) + (("Yuri" "Gagarin" "gagarin@soviet.org")) + (("Vladimir" "Lenin" "lenin@soviet.org"))) + +;; starts a transaction deletes a record and then rolls back the deletion +(deftest :fdml/transaction/1 + (with-dataset *ds-employees* + (let ((results '())) + ;; test if we are in a transaction + (push (clsql:in-transaction-p) results) + ;;start a transaction + (clsql:start-transaction) + ;; test if we are in a transaction + (push (clsql:in-transaction-p) results) + ;;Putin has got to go + (clsql:delete-records :from [employee] :where [= [last-name] "Putin"]) + ;;Should be nil + (push + (clsql:select [*] :from [employee] :where [= [last-name] "Putin"]) + results) + ;;Oh no, he's still there + (clsql:rollback) + ;; test that we are out of the transaction + (push (clsql:in-transaction-p) results) + ;; Check that we got him back alright + (push (clsql:select [email] :from [employee] :where [= [last-name] "Putin"] + :flatp t) + results) + (apply #'values (nreverse results)))) + nil t nil nil ("putin@soviet.org")) + +;; starts a transaction, updates a record and then rolls back the update +(deftest :fdml/transaction/2 + (with-dataset *ds-employees* + (let ((results '())) + ;; test if we are in a transaction + (push (clsql:in-transaction-p) results) + ;;start a transaction + (clsql:start-transaction) + ;; test if we are in a transaction + (push (clsql:in-transaction-p) results) + ;;Putin has got to go + (clsql:update-records [employee] + :av-pairs '((email "putin-nospam@soviet.org")) + :where [= [last-name] "Putin"]) + ;;Should be new value + (push (clsql:select [email] :from [employee] + :where [= [last-name] "Putin"] + :flatp t) + results) + ;;Oh no, he's still there + (clsql:rollback) + ;; test that we are out of the transaction + (push (clsql:in-transaction-p) results) + ;; Check that we got him back alright + (push (clsql:select [email] :from [employee] :where [= [last-name] "Putin"] + :flatp t) + results) + (apply #'values (nreverse results)))) + nil t ("putin-nospam@soviet.org") nil ("putin@soviet.org")) + +;; runs an update within a transaction and checks it is committed +(deftest :fdml/transaction/3 + (with-dataset *ds-employees* + (let ((results '())) + ;; check status + (push (clsql:in-transaction-p) results) + ;; update records + (push + (clsql:with-transaction () + (clsql:update-records [employee] + :av-pairs '((email "lenin-nospam@soviet.org")) + :where [= [emplid] 1])) + results) + ;; check status + (push (clsql:in-transaction-p) results) + ;; check that was committed + (push (clsql:select [email] :from [employee] :where [= [emplid] 1] + :flatp t) + results) + ;; undo the changes + (push + (clsql:with-transaction () + (clsql:update-records [employee] + :av-pairs '((email "lenin@soviet.org")) + :where [= [emplid] 1])) + results) + ;; and check status + (push (clsql:in-transaction-p) results) + ;; check that was committed + (push (clsql:select [email] :from [employee] :where [= [emplid] 1] + :flatp t) + results) + (apply #'values (nreverse results)))) + nil nil nil ("lenin-nospam@soviet.org") nil nil ("lenin@soviet.org")) + +;; runs a valid update and an invalid one within a transaction and checks +;; that the valid update is rolled back when the invalid one fails. +(deftest :fdml/transaction/4 + (with-dataset *ds-employees* + (let ((results '())) + ;; check status + (push (clsql:in-transaction-p) results) + (handler-case + (clsql:with-transaction () + ;; valid update + (clsql:update-records [employee] + :av-pairs '((email "lenin-nospam@soviet.org")) + :where [= [emplid] 1]) + ;; invalid update which generates an error + (clsql:update-records [employee] + :av-pairs + '((emale "lenin-nospam@soviet.org")) + :where [= [emplid] 1])) + (clsql:sql-database-error () + (progn + ;; check status + (push (clsql:in-transaction-p) results) + ;; and check nothing done + (push (clsql:select [email] :from [employee] :where [= [emplid] 1] + :flatp t) + results) + (apply #'values (nreverse results))))))) + nil nil ("lenin@soviet.org")) + + +)) + diff --git a/tests/test-i18n.lisp b/tests/test-i18n.lisp new file mode 100644 index 0000000..b00c6d5 --- /dev/null +++ b/tests/test-i18n.lisp @@ -0,0 +1,52 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-i18n.lisp +;;;; Purpose: Tests for passing non-ascii encoded strings to db and back +;;;; Author: Nathan Bird & Kevin M. Rosenberg +;;;; Created: Feb 2010 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + +(setq *rt-i18n* + '( + +;;; The point of these two is to require proper encoding support +;;; UTF-8 for example can handle these easily. +;; I show this as a 20char string and 27 bytes in utf-8 +(deftest :basic/i18n/1 + (let ((uffi:*default-foreign-encoding* :utf-8)) + (first (query "SELECT 'Iñtërnâtiônàlizætiøn'" + :flatp t :field-names nil))) + "Iñtërnâtiônàlizætiøn") + +;; the z in this one is even stronger +;; I show this as a 20char string and 28 bytes in utf-8 +(deftest :basic/i18n/2 + (let ((uffi:*default-foreign-encoding* :utf-8)) + (first (query "SELECT 'Iñtërnâtiônàližætiøn'" + :flatp t :field-names nil))) + "Iñtërnâtiônàližætiøn") + +(deftest :basic/i18n/big/1 + (let ((test-string (with-output-to-string (str) + (dotimes (n 250) + (write-sequence "Iñtërnâtiônàližætiøn" str))))) + (with-dataset *ds-bigtext* + (clsql-sys:execute-command + (format nil + "INSERT INTO testbigtext (a) VALUES ('~a')" + test-string)) + (let ((res (first (clsql:query "SELECT a from testbigtext" :flatp t :field-names nil)))) + (assert (equal test-string res) (test-string res) + "Returned internationalization string was incorrect. Test :basic/i18n/big/1"))))) + +)) diff --git a/tests/test-init.lisp b/tests/test-init.lisp new file mode 100644 index 0000000..8312784 --- /dev/null +++ b/tests/test-init.lisp @@ -0,0 +1,399 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ====================================================================== +;;;; File: test-init.lisp +;;;; Authors: Marcus Pearce , Kevin Rosenberg +;;;; Created: 30/03/2004 +;;;; +;;;; Initialisation utilities for running regression tests on CLSQL. +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ====================================================================== + +(in-package #:clsql-tests) + +(defvar *report-stream* *standard-output* "Stream to send text report.") +(defvar *sexp-report-stream* nil "Stream to send sexp report.") +(defvar *rt-internal*) +(defvar *rt-basic*) +(defvar *rt-connection*) +(defvar *rt-fddl*) +(defvar *rt-fdml*) +(defvar *rt-ooddl*) +(defvar *rt-oodml*) +(defvar *rt-syntax*) +(defvar *rt-time*) +(defvar *rt-pool*) +;; Below must be set as nil since test-i18n.lisp is not loaded on all platforms. +(defvar *rt-i18n* nil) + +(defvar *test-database-type* nil) +(defvar *test-database-underlying-type* nil) +(defvar *test-database-user* nil) +(defvar *test-false-database-user* "adsfjalsdkfjlakjsdfl" + "For testing ownership, a user that isn't the owner.") +(defvar *test-start-utime* nil) +(defvar *test-connection-spec* nil) +(defvar *test-connection-db-type* nil) +(defvar *test-report-width* 80 "Width of test report in ems.") + + +(defun find-test-connection-spec (db-type &key position) + (nth (or position 0) + (db-type-spec db-type (read-specs)))) + +(defun test-connect + (&key + (db-type *test-database-type* db-type-p) + position pool spec) + (unless spec + (setf spec + (or (and (null db-type-p) *test-connection-spec*) + (find-test-connection-spec db-type :position position)))) + (when *default-database* + (disconnect :database *default-database*)) + (setf *test-database-type* db-type + *test-database-user* + (cond + ((member db-type '(:oracle :odbc :aodbc)) (second spec)) + ((>= (length spec) 3) (third spec))) + *test-connection-spec* spec + *default-database* + (clsql:connect + spec + :database-type db-type + :make-default t + :if-exists :old + :pool pool) + *test-database-underlying-type* + (clsql-sys:database-underlying-type *default-database*)) + *default-database*) + +(defun test-setup-database (db-type &key (spec (find-test-connection-spec db-type))) + (when (clsql-sys:db-backend-has-create/destroy-db? db-type) + (ignore-errors (destroy-database spec :database-type db-type)) + (ignore-errors (create-database spec :database-type db-type))) + + ;; Connect to the database + (test-connect :db-type db-type :spec spec) + ;; Ensure database is empty + (truncate-database :database *default-database*) + + ;; If Postgres, turn off notices to console + (when (eql db-type :postgresql) + (clsql:execute-command "SET client_min_messages = WARNING")) + + *default-database*) + +(defun default-suites () + "The default list of tests to run." + (append *rt-connection* *rt-basic* *rt-fddl* *rt-fdml* + *rt-ooddl* *rt-oodml* *rt-syntax* *rt-time* *rt-i18n*)) + +(defun internal-suites () + "The default internal suites that should run without any specific backend" + (append *rt-internal* *rt-pool*)) + + +(defvar *error-count* 0) +(defvar *error-list* nil) + +(defun run-function-append-report-file (function report-file) + (let* ((report-path (etypecase report-file + (pathname report-file) + (string (parse-namestring report-file)))) + (sexp-report-path (make-pathname :defaults report-path + :type "sexp"))) + (with-open-file (rs report-path :direction :output + :if-exists :append + :if-does-not-exist :create) + (with-open-file (srs sexp-report-path :direction :output + :if-exists :append + :if-does-not-exist :create) + (funcall function :report-stream rs :sexp-report-stream srs))))) + +(defun run-tests-append-report-file (report-file) + (run-function-append-report-file 'run-tests report-file)) + + +(defun run-tests (&key (report-stream *standard-output*) (sexp-report-stream nil) + (suites (append (internal-suites) (default-suites)))) + ;; clear SQL-OUTPUT cache + (setq clsql-sys::*output-hash* (make-hash-table :test #'equal)) + (setf *test-database-underlying-type* nil) + (let ((specs (read-specs)) + (*report-stream* report-stream) + (*sexp-report-stream* sexp-report-stream) + (*error-count* 0) + (*error-list* nil)) + (unless specs + (warn "Not running tests because test configuration file is missing") + (return-from run-tests :skipped)) + (load-necessary-systems specs) + ;;run the internal suites + (do-tests-for-internals :suites (intersection suites (internal-suites))) + ;; run backend-specific tests + (let ((suites (intersection suites (default-suites)))) + (when suites + (dolist (db-type +all-db-types+) + (dolist (spec (db-type-spec db-type specs)) + (format report-stream "~%~%Start Running Tests Against: ~A ~A~%~%" db-type (ignore-errors (subseq spec 0 2))) + (do-tests-for-backend db-type spec :suites suites) + (format report-stream "~%~%Finished Running Tests Against: ~A ~A~%~%" db-type (ignore-errors (subseq spec 0 2))))))) + (zerop *error-count*))) + +(defun load-necessary-systems (specs) + (dolist (db-type +all-db-types+) + (when (db-type-spec db-type specs) + (clsql-sys:initialize-database-type :database-type db-type)))) + +(defun write-report-banner (report-type db-type stream db-name) + (format stream + "~& +****************************************************************************** +*** CLSQL ~A begun at ~A +*** ~A +*** ~A on ~A +*** Database ~:@(~A~) +*** Type: ~:@(~A~) backend~A. +****************************************************************************** +" + report-type + (clsql:format-time + nil + (clsql:utime->time (get-universal-time))) + (lisp-implementation-type) + (lisp-implementation-version) + (machine-type) + db-name + db-type + (if (not (eq db-type *test-database-underlying-type*)) + (format nil " with underlying type ~:@(~A~)" + *test-database-underlying-type*) + "") + )) + +(defun do-tests-for-internals (&key (suites (internal-suites))) + (write-report-banner "Test Suite" "CLSQL Internals" *report-stream* + "N/A") + (%do-tests suites nil)) + +(defun %do-tests (test-forms db-type) + (regression-test:rem-all-tests) + (dolist (test-form test-forms) + (eval test-form)) + + (let* ((cl:*print-right-margin* *test-report-width*) + (remaining (regression-test:do-tests *report-stream*))) + (when (regression-test:pending-tests) + (incf *error-count* (length remaining)))) + + (let ((sexp-error (list db-type + *test-database-underlying-type* + (get-universal-time) + (length test-forms) + (regression-test:pending-tests) + (lisp-implementation-type) + (lisp-implementation-version) + (machine-type)))) + (when *sexp-report-stream* + (write sexp-error :stream *sexp-report-stream* :readably t)) + (push sexp-error *error-list*)) + ) + +(defun do-tests-for-backend (db-type spec &key + (suites (default-suites)) ) + (test-setup-database db-type :spec spec) + (unwind-protect + (multiple-value-bind (test-forms skip-tests) + (compute-tests-for-backend db-type *test-database-underlying-type* :suites suites) + + (write-report-banner "Test Suite" db-type *report-stream* + (database-name-from-spec spec db-type)) + + (%do-tests test-forms db-type) + + (format *report-stream* "~&~D of ~D Tests skipped:" + (length skip-tests) + (length test-forms)) + (if skip-tests + (let ((max-test-name (length (symbol-name (caar skip-tests))))) + (dolist (skipped (cdr skip-tests)) + (let ((len (length (symbol-name (car skipped))))) + (when (> len max-test-name) + (setq max-test-name len)))) + (let ((fmt (format nil "~~& ~~~DA ~~A~~%" max-test-name))) + (dolist (skipped skip-tests) + ;; word-wrap the reason string field + (let* ((test (car skipped)) + (reason (cdr skipped)) + ;; (rlen (length reason)) + (rwidth (max 20 (- (or *test-report-width* 80) max-test-name 3))) + (rwords (clsql-sys::delimited-string-to-list reason #\space t)) + (rformat (format nil "~~{~~<~%~~1,~D:;~~A~~> ~~}" rwidth)) + (rwrapped (format nil rformat rwords)) + (rlines (clsql-sys::delimited-string-to-list rwrapped #\Newline t))) + (dolist (rline rlines) + (format *report-stream* fmt (if test + (prog1 + test + (setq test nil)) + "") + rline)))))) + (format *report-stream* " None~%"))) + (disconnect))) + + +(defun compute-tests-for-backend (db-type db-underlying-type + &key (suites (default-suites))) + (let ((test-forms '()) + (skip-tests '())) + (dolist (test-form (if (listp suites) suites (list suites))) + (let ((test (second test-form))) + (cond + ((and (not (eql db-underlying-type :mysql)) + (clsql-sys:in test :connection/query-command + :basic/reallybigintegers/1 + :connection/pool/procedure-mysql)) + (push (cons test "known to work only in MySQL as yet.") skip-tests)) + ((and (null (clsql-sys:db-type-has-views? db-underlying-type)) + (clsql-sys:in test :fddl/view/1 :fddl/view/2 :fddl/view/3 :fddl/view/4)) + (push (cons test "views not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-has-boolean-where? db-underlying-type)) + (clsql-sys:in test :fdml/select/11 :oodml/select/5)) + (push (cons test "boolean where not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-has-subqueries? db-underlying-type)) + (clsql-sys:in test :fdml/select/5 :fdml/select/10 + :fdml/select/32 :fdml/select/33)) + (push (cons test "subqueries not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-transaction-capable? db-underlying-type + *default-database*)) + (clsql-sys:in test :fdml/transaction/1 :fdml/transaction/2 :fdml/transaction/3 :fdml/transaction/4)) + (push (cons test "transactions not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-has-fancy-math? db-underlying-type)) + (clsql-sys:in test :fdml/select/1)) + (push (cons test "fancy math not supported.") skip-tests)) + ((and (eql *test-database-type* :sqlite) + (clsql-sys:in test :fddl/view/4 :fdml/select/10 + :fdml/select/21 :fdml/select/32 + :fdml/select/33)) + (push (cons test "not supported by sqlite.") skip-tests)) + ((and (eql *test-database-type* :sqlite3) + (clsql-sys:in test :fddl/view/4 :fdml/select/10 + :fdml/select/21 :fdml/select/32 + :fdml/select/33)) + (push (cons test "not supported by sqlite3.") skip-tests)) + ((and (not (clsql-sys:db-type-has-bigint? db-type)) + (clsql-sys:in test :basic/bigint/1)) + (push (cons test "bigint not supported.") skip-tests)) + ((and (eql *test-database-underlying-type* :mysql) + (clsql-sys:in test :fdml/select/26)) + (push (cons test "string table aliases not supported on all MySQL versions.") skip-tests)) + ((and (eql *test-database-underlying-type* :mysql) + (clsql-sys:in test :fdml/select/22 :fdml/query/5 + :fdml/query/7 :fdml/query/8)) + (push (cons test "not supported by mysql.") skip-tests)) + ((and (null (clsql-sys:db-type-has-union? db-underlying-type)) + (clsql-sys:in test :fdml/query/6 :fdml/select/31)) + (push (cons test "union not supported") skip-tests)) + ((and (eq *test-database-type* :oracle) + (clsql-sys:in test :fdml/query/8 :fdml/select/21 + :fddl/table/6)) + (push (cons test "syntax not supported.") skip-tests)) + ((and (eq *test-database-type* :odbc) + (eq *test-database-underlying-type* :postgresql) + (clsql-sys:in test :fddl/owner/1 :fddl/owner/table + :fddl/owner/attributes + :fddl/owner/attribute-types + :fddl/owner/index + :fddl/owner/sequence)) + (push (cons test "table ownership not supported by postgresql odbc driver.") skip-tests)) + ((and (not (member *test-database-underlying-type* + '(:postgresql :oracle))) + (clsql-sys:in test :fddl/owner/1 :fddl/owner/table + :fddl/owner/attributes + :fddl/owner/attribute-types + :fddl/owner/index + :fddl/owner/sequence)) + (push (cons test "table ownership not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-has-intersect? db-underlying-type)) + (clsql-sys:in test :fdml/query/7)) + (push (cons test "intersect not supported.") skip-tests)) + ((and (null (clsql-sys:db-type-has-except? db-underlying-type)) + (clsql-sys:in test :fdml/query/8)) + (push (cons test "except not supported.") skip-tests)) + ((and (eq *test-database-underlying-type* :mssql) + (clsql-sys:in test :fdml/select/9)) + (push (cons test "mssql uses integer math for AVG.") skip-tests)) + ((and (not (member *test-database-underlying-type* + '(:postgresql :mysql :sqlite3 ))) + (clsql-sys:in test :fdml/select/37 :fdml/select/38)) + (push (cons test "LIMIT keyword not supported in SELECT.") skip-tests)) + ((and (not (clsql-sys:db-type-has-auto-increment? db-underlying-type)) + (clsql-sys:in test :oodml/select/12 :oodml/select/13 :oodml/select/14 + :oodml/select/15 :oodml/select/16 :oodml/select/17 + :oodml/select/18 :oodml/select/19 :oodml/select/20 + :oodml/select/21 :oodml/select/22 :oodml/select/23 + :oodml/update-records/4 :oodml/update-records/4-slots + :oodml/update-records/5 :oodml/update-records/5-slots + :oodml/update-records/6 :oodml/update-records/7 + :oodml/update-records/8 :oodml/update-records/9 + :oodml/update-records/9-slots :oodml/update-records/10 + :oodml/update-records/11 :OODML/UPDATE-RECORDS/12 :oodml/update-instance/3 + :oodml/update-instance/4 :oodml/update-instance/5 + :oodml/update-instance/6 :oodml/update-instance/7 + :oodml/db-auto-sync/3 :oodml/db-auto-sync/4)) + (push (cons test ":auto-increment not supported.") skip-tests)) + ((and (not (member *test-database-underlying-type* + '(:postgresql :postgresql-socket))) + (clsql-sys:in test + :time/pg/fdml/usec :time/pg/oodml/no-usec :time/pg/oodml/usec)) + (push (cons test "Postgres specific test.") + skip-tests)) + ((and (eql *test-database-type* :postgresql-socket3) + (clsql-sys:in test :BASIC/SELECT/2 :basic/select/3)) + (push (cons test "Postgres-socket3 always auto types") + skip-tests)) + ((and (eql *test-database-type* :postgresql-socket3) + (clsql-sys:in test :fdml/select/18)) + (push (cons test "Postgres-socket3 doesnt support attribute based type coersion") + skip-tests)) + ((and (eql *test-database-type* :postgresql-socket3) + (clsql-sys:in test :basic/map/1 :basic/map/2 :basic/map/3 :basic/map/4 + :basic/do/1 :basic/do/2 :fdml/do-query/1 :fdml/map-query/1 + :fdml/map-query/2 :fdml/map-query/3 :fdml/map-query/4 :fdml/loop/1 + :fdml/loop/2 :fdml/loop/3 + )) + (push (cons test "postgresql-socket3 doesnt support cursoring interface") + skip-tests)) + ((and (member *test-database-underlying-type* '(:mysql)) + (clsql-sys:in test :time/cross-platform/msec + :time/cross-platform/usec/no-tz :time/cross-platform/usec/tz)) + (push (cons test "MySQL doesn't support fractional seconds on timestamp columns (http://forge.mysql.com/worklog/task.php?id=946).") + skip-tests)) + ((and (member *test-database-underlying-type* '(:mssql)) + (clsql-sys:in test :time/cross-platform/usec/no-tz :time/cross-platform/usec/tz)) + (push (cons test "MSSQL doesn't support micro-seconds on datetime columns.") + skip-tests)) + (t + (push test-form test-forms))))) + (values (nreverse test-forms) (nreverse skip-tests)))) + +(defun rapid-load (type &optional (position 0)) + "Rapid load for interactive testing." + (test-setup-database + type + :spec (find-test-connection-spec type :position position)) + *default-database*) + +(defun rl () + (rapid-load :postgresql)) + +(defun rlm () + (rapid-load :mysql)) + +(defun rlo () + (rapid-load :oracle)) diff --git a/tests/test-internal.lisp b/tests/test-internal.lisp new file mode 100644 index 0000000..61076c6 --- /dev/null +++ b/tests/test-internal.lisp @@ -0,0 +1,77 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-internal.lisp +;;;; Purpose: Tests for internal clsql functions +;;;; Author: Kevin M. Rosenberg +;;;; Created: May 2004 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) +(clsql-sys:file-enable-sql-reader-syntax) + +(setq *rt-internal* + '( + (deftest :int/convert/1 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR") + "SELECT FOO FROM BAR") + + (deftest :int/convert/2 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR WHERE ID=?") + "SELECT FOO FROM BAR WHERE ID=$1") + + (deftest :int/convert/3 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM \"BAR\" WHERE ID=? AND CODE=?") + "SELECT FOO FROM \"BAR\" WHERE ID=$1 AND CODE=$2") + + (deftest :int/convert/4 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT FOO FROM BAR WHERE ID=\"Match?\" AND CODE=?") + "SELECT FOO FROM BAR WHERE ID=\"Match?\" AND CODE=$1") + + (deftest :int/convert/5 + (clsql-sys::prepared-sql-to-postgresql-sql "SELECT 'FOO' FROM BAR WHERE ID='Match?''?' AND CODE=?") + "SELECT 'FOO' FROM BAR WHERE ID='Match?''?' AND CODE=$1") + + (deftest :int/output-caching/1 + ;; ensure that key generation and matching is working + ;; so that this table doesnt balloon (more than designed) + (list + (progn (clsql:sql [foo]) + (clsql:sql [foo]) + (hash-table-count clsql-sys::*output-hash*)) + + (progn (clsql:sql [foo.bar]) + (clsql:sql [foo bar]) + (hash-table-count clsql-sys::*output-hash*)) + (progn (clsql:sql (clsql-sys:sql-expression + :table (clsql-sys::database-identifier 'foo) + :attribute (clsql-sys::database-identifier 'bar))) + (clsql:sql (clsql-sys:sql-expression + :table (clsql-sys::database-identifier 'foo) + :attribute (clsql-sys::database-identifier 'bar))) + (hash-table-count clsql-sys::*output-hash*))) + (1 2 2)) + + (deftest :int/output-caching/2 + ;; ensure that we can disable the output cache and + ;; still have everything work + (let ((clsql-sys::*output-hash*)) + (list (clsql:sql [foo]) (clsql:sql [foo]) (clsql:sql [foo.bar]))) + ("FOO" "FOO" "FOO.BAR")) + + (deftest :currency/read-value/1 + (list + (clsql-sys::read-decimal-value "$ 10,500.30") + (clsql-sys::read-decimal-value "$ 10.500,30") + (clsql-sys::read-decimal-value "-10 500,30") + (clsql-sys::read-decimal-value "$ 10.500,30")) + (1050030/100 1050030/100 -1050030/100 1050030/100)) + + )) diff --git a/tests/test-ooddl.lisp b/tests/test-ooddl.lisp new file mode 100644 index 0000000..9037e53 --- /dev/null +++ b/tests/test-ooddl.lisp @@ -0,0 +1,202 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-ooddl.lisp +;;;; Purpose: Tests for the CLSQL Object Oriented Data Definition Language +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + + +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) + + +(def-view-class big () + ((i :type integer :initarg :i) + (bi :type bigint :initarg :bi))) + +(def-dataset *ds-big* + (:setup (lambda () + (clsql-sys:create-view-from-class 'big) + (let ((max (expt 2 60))) + (dotimes (i 555) + (update-records-from-instance + (make-instance 'big :i (1+ i) :bi (truncate max (1+ i)))))))) + (:cleanup + (lambda () (clsql-sys:drop-view-from-class 'big)))) + +(setq *rt-ooddl* + '( + +;; Ensure slots inherited from standard-classes are :virtual +(deftest :ooddl/metaclass/1 + (values + (clsql-sys::view-class-slot-db-kind + (clsql-sys::slotdef-for-slot-with-class 'extraterrestrial + (find-class 'person))) + (clsql-sys::view-class-slot-db-kind + (clsql-sys::slotdef-for-slot-with-class 'hobby (find-class 'person)))) + :virtual :virtual) + +;; Ensure all slots in view-class are view-class-effective-slot-definition +(deftest :ooddl/metaclass/2 + (values + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'person))) + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'employee))) + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'setting))) + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'theme))) + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'node))) + (every #'(lambda (slotd) + (typep slotd 'clsql-sys::view-class-effective-slot-definition)) + (clsql-sys::class-slots (find-class 'company)))) + t t t t t t) + +;; Ensure classes are correctly marked normalized or not, default not +;(deftest :ooddl/metaclass/3 +; (values +; (clsql-sys::normalizedp derivednode1) +; (clsql-sys::normalizedp basenode) +; (clsql-sys::normalizedp company1) +; (clsql-sys::normalizedp employee3) +; (clsql-sys::normalizedp derivednode-sc-2)) +; t nil nil nil t) + +;(deftest :ooddl/metaclass/3 +; (values +; (normalizedp (find-class 'baseclass)) +; (normalizedp (find-class 'normderivedclass))) +; nil t) + +(deftest :ooddl/join/1 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) (slot-value e 'ecompanyid)) + (company-employees company1))) + (1 1 1 1 1 1 1 1 1 1)) + +(deftest :ooddl/join/2 + (with-dataset *ds-employees* + (slot-value (president company1) 'last-name)) + "Lenin") + +(deftest :ooddl/join/3 + (with-dataset *ds-employees* + (slot-value (employee-manager employee2) 'last-name)) + "Lenin") + +(deftest :ooddl/join/4 + (with-dataset *ds-employees* + (values + (length (employee-addresses employee10)) + ;; add an address + (let ((*db-auto-sync* T)) + (make-instance 'address :addressid 50) + (make-instance 'employee-address :emplid 10 :addressid 50) + ;; again + (length (employee-addresses employee10))) + (progn + (update-objects-joins (list employee10) :slots '(addresses)) + (length (employee-addresses employee10))))) + 0 0 1) + +(deftest :ooddl/big/1 + ;;tests that we can create-view-from-class with a bigint slot, + ;; and stick a value in there. + (progn (clsql-sys:create-view-from-class 'big) + (values + (clsql:table-exists-p [big] ) + (progn + (clsql:drop-table [big] :if-does-not-exist :ignore) + (clsql:table-exists-p [big] ))) + ) + t nil) + +(deftest :ooddl/big/2 + (with-dataset *ds-big* + (let ((rows (clsql:select [*] :from [big] :order-by [i] :field-names nil))) + (values + (length rows) + (do ((i 0 (1+ i)) + (max (expt 2 60)) + (rest rows (cdr rest))) + ((= i (length rows)) t) + (let ((index (1+ i)) + (int (first (car rest))) + (bigint (second (car rest)))) + (when (and (or (eq *test-database-type* :oracle) + (and (eq *test-database-type* :odbc) + (eq *test-database-underlying-type* :postgresql))) + (stringp bigint)) + (setf bigint (parse-integer bigint))) + (unless (and (eql int index) + (eql bigint (truncate max index))) + (return nil))))))) + 555 t) + +(deftest :ooddl/time/1 + (with-dataset *ds-employees* + (sleep 1) ;force birthdays into the past + (let* ((now (clsql:get-time))) + (when (member *test-database-underlying-type* '(:postgresql :postgresql-socket)) + (clsql:execute-command "set datestyle to 'iso'")) + (clsql:update-records [employee] :av-pairs `((birthday ,now)) + :where [= [emplid] 1]) + (let ((dbobj (car (clsql:select 'employee :where [= [birthday] now] + :flatp t)))) + (values + (slot-value dbobj 'last-name) + (clsql:time= (slot-value dbobj 'birthday) now))))) + "Lenin" t) + +(deftest :ooddl/time/2 + (with-dataset *ds-employees* + (sleep 1) ;force birthdays into the past + (let* ((now (clsql:get-time)) + (fail-index -1)) + (when (member *test-database-underlying-type* '(:postgresql :postgresql-socket)) + (clsql:execute-command "set datestyle to 'iso'")) + (dotimes (x 40) + (clsql:update-records [employee] :av-pairs `((birthday ,now)) + :where [= [emplid] 1]) + (let ((dbobj (car (clsql:select 'employee :where [= [birthday] now] + :flatp t)))) + (unless (clsql:time= (slot-value dbobj 'birthday) now) + (setf fail-index x)) + (setf now (clsql:roll now :day (* 10 x))))) + fail-index)) + -1) + +(deftest :ooddl/time/3 + (with-dataset *ds-employees* + (progn + (when (member *test-database-underlying-type* '(:postgresql :postgresql-socket)) + (clsql:execute-command "set datestyle to 'iso'")) + (let ((dbobj (car (clsql:select 'employee :where [= [emplid] 10] + :flatp t)))) + (list + (eql *test-start-utime* (slot-value dbobj 'bd-utime)) + (clsql:time= (slot-value dbobj 'birthday) + (clsql:utime->time (slot-value dbobj 'bd-utime))))))) + (t t)) + +)) + + diff --git a/tests/test-oodml.lisp b/tests/test-oodml.lisp new file mode 100644 index 0000000..6278d73 --- /dev/null +++ b/tests/test-oodml.lisp @@ -0,0 +1,1242 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ====================================================================== +;;;; File: test-oodml.lisp +;;;; Created: 01/04/2004 +;;;; +;;;; Tests for the CLSQL Object Oriented Data Definition Language +;;;; (OODML). +;;;; +;;;; This file is part of CLSQL. +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ====================================================================== + +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) + +(defmacro has-sql-value-conversion-error (() &body body) + `(let (*debugger-hook*) + (handler-case + (progn ,@body nil) + (clsql-sys::sql-value-conversion-error (c) + (declare (ignore c)) + t)))) + +(setq *rt-oodml* + '( + +(deftest :oodml/read-symbol-value/1-into-this-package + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'symbol 'clsql-tests::foo nil nil) + 'symbol nil nil) + clsql-tests::foo) + +(deftest :oodml/read-symbol-value/2-into-another-pacakge + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'symbol 'clsql-sys::foo nil nil) + 'symbol nil nil) + clsql-sys::foo) + +(deftest :oodml/read-symbol-value/3-keyword + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'keyword ':foo nil nil) + 'keyword nil nil) + :foo) + +(deftest :oodml/read-symbol-value/4-keyword-error + (has-sql-value-conversion-error () + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'keyword 'foo nil nil) + 'keyword nil nil)) + T) + +(deftest :oodml/read-symbol-value/5-unknown-type-error-1 + (has-sql-value-conversion-error () + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'bloop 'foo nil nil) + 'bloop nil nil)) + t) + +(deftest :oodml/read-symbol-value/6-unknown-type-error-2 + (has-sql-value-conversion-error () + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type 'bloop 'foo nil nil) + '(or integer float) nil nil)) + t) + +(deftest :oodml/read-symbol-value/read-list + (clsql-sys::read-sql-value + (clsql-sys::database-output-sql-as-type + 'list '(("status" "new" "open")) nil nil) + 'list nil nil) + (("status" "new" "open"))) + +(deftest :oodml/select/1 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) (slot-value e 'last-name)) + (clsql:select 'employee :order-by [last-name] :flatp t :caching nil))) + ("Andropov" "Brezhnev" "Chernenko" "Gorbachev" "Kruschev" "Lenin" "Putin" + "Stalin" "Trotsky" "Yeltsin")) + +(deftest :oodml/select/2 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) (slot-value e 'name)) + (clsql:select 'company :flatp t :caching nil))) + ("Widgets Inc.")) + +(deftest :oodml/select/3 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) (slot-value e 'ecompanyid)) + (clsql:select 'employee + :where [and [= [slot-value 'employee 'ecompanyid] + [slot-value 'company 'companyid]] + [= [slot-value 'company 'name] + "Widgets Inc."]] + :flatp t + :caching nil))) + (1 1 1 1 1 1 1 1 1 1)) + +(deftest :oodml/select/4 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) + (concatenate 'string (slot-value e 'first-name) + " " + (slot-value e 'last-name))) + (clsql:select 'employee :where [= [slot-value 'employee 'first-name] + "Vladimir"] + :flatp t + :order-by [last-name] + :caching nil))) + ("Vladimir Lenin" "Vladimir Putin")) + +(deftest :oodml/select/5 + (with-dataset *ds-employees* + (length (clsql:select 'employee :where [married] :flatp t :caching nil))) + 3) + +(deftest :oodml/select/6 + (with-dataset *ds-employees* + (let ((a (caar (clsql:select 'address :where [= 1 [addressid]] :caching nil)))) + (values + (slot-value a 'street-number) + (slot-value a 'street-name) + (slot-value a 'city) + (slot-value a 'postal-code)))) + 10 "Park Place" "Leningrad" 123) + +(deftest :oodml/select/7 + (with-dataset *ds-employees* + (let ((a (caar (clsql:select 'address :where [= 2 [addressid]] :caching nil)))) + (values + (slot-value a 'street-number) + (slot-value a 'street-name) + (slot-value a 'city) + (slot-value a 'postal-code)))) + nil "" "no city" 0) + +(deftest :oodml/select/8 + (with-dataset *ds-employees* + (mapcar #'(lambda (e) (slot-value e 'married)) + (clsql:select 'employee :flatp t :order-by [emplid] :caching nil))) + (t t t nil nil nil nil nil nil nil)) + +(deftest :oodml/select/9 + (with-dataset *ds-employees* + (mapcar #'(lambda (pair) + (list + (typep (car pair) 'address) + (typep (second pair) 'employee-address) + (slot-value (car pair) 'addressid) + (slot-value (second pair) 'aaddressid) + (slot-value (second pair) 'aemplid))) + (employee-addresses employee1))) + ((t t 1 1 1) (t t 2 2 1))) + +(deftest :oodml/select/10 + (with-dataset *ds-employees* + (mapcar #'(lambda (pair) + (list + (typep (car pair) 'address) + (typep (second pair) 'employee-address) + (slot-value (car pair) 'addressid) + (slot-value (second pair) 'aaddressid) + (slot-value (second pair) 'aemplid))) + (employee-addresses employee2))) + ((t t 2 2 2))) + +(deftest :oodml/select/11 + (with-dataset *ds-employees* + (values (mapcar #'(lambda (x) (slot-value x 'emplid)) + (clsql:select 'employee :order-by '(([emplid] :asc)) + :flatp t)) + (mapcar #'(lambda (x) (slot-value x 'emplid)) + (clsql:select 'employee :order-by '(([emplid] :desc)) + :flatp t)))) + (1 2 3 4 5 6 7 8 9 10) + (10 9 8 7 6 5 4 3 2 1)) + +;; test retrieval of node, derived nodes etc +(deftest :oodml/select/12 + (with-dataset *ds-nodes* + (length (clsql:select 'node :where [not [null [node-id]]] :flatp t :caching nil))) + 11) + +(deftest :oodml/select/13 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'node :where [= 1 [node-id]] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'title)))) + 1 "Bare node") + +(deftest :oodml/select/14 + (with-dataset *ds-nodes* + (length (clsql:select 'setting :where [not [null [setting-id]]] :flatp t :caching nil))) + 4) + +(deftest :oodml/select/15 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'setting :where [= 3 [setting-id]] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'setting-id) + (slot-value a 'title) + (slot-value a 'vars)))) + 3 3 "Setting2" "var 2") + +(deftest :oodml/select/16 + (with-dataset *ds-nodes* + (length (clsql:select 'user :where [not [null [user-id]]] :flatp t :caching nil))) + 2) + +(deftest :oodml/select/17 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'user :where [= 4 [user-id]] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'user-id) + (slot-value a 'title) + (slot-value a 'nick)))) + 4 4 "user-1" "first user") + +(deftest :oodml/select/18 + (with-dataset *ds-nodes* + (length (clsql:select 'theme :where [not [null [theme-id]]] :flatp t :caching nil))) + 2) + +(deftest :oodml/select/19 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'theme :where [= 6 [theme-id]] :flatp t :caching nil)))) + (slot-value a 'theme-id))) + 6) + +(deftest :oodml/select/20 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'theme :where [= 7 [theme-id]] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'theme-id) + (slot-value a 'title) + (slot-value a 'vars) + (slot-value a 'doc) + ))) + 7 7 "theme-2" + nil "second theme") + +;; Some tests to check weird subclassed nodes (node without own table, or subclassed of same) +(deftest :oodml/select/21 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'location :where [= [title] "location-1"] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'title)))) + 8 "location-1") + +(deftest :oodml/select/22 + (with-dataset *ds-nodes* + (let ((a (car (clsql:select 'subloc :where [not [null [subloc-id]]] :flatp t :caching nil)))) + (values + (slot-value a 'node-id) + (slot-value a 'subloc-id) + (slot-value a 'title) + (slot-value a 'loc)))) + 10 10 "subloc-1" "a subloc") + +(deftest :oodml/select/23 + (with-dataset *ds-artists* + (length (clsql:select 'artist :flatp t :caching nil))) + 0) + + + +;; test retrieval is deferred +(deftest :oodm/retrieval/1 + (with-dataset *ds-employees* + (every #'(lambda (e) (not (slot-boundp e 'company))) + (select 'employee :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/2 + (with-dataset *ds-employees* + (every #'(lambda (e) (not (slot-boundp e 'address))) + (select 'deferred-employee-address :flatp t :caching nil))) + t) + +;; :retrieval :immediate should be boundp before accessed +(deftest :oodm/retrieval/3 + (with-dataset *ds-employees* + (every #'(lambda (ea) (slot-boundp ea 'address)) + (select 'employee-address :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/4 + (with-dataset *ds-employees* + (every #'(lambda (ea) (typep (slot-value ea 'address) 'address)) + (select 'employee-address :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/5 + (with-dataset *ds-employees* + (every #'(lambda (ea) (typep (slot-value ea 'address) 'address)) + (select 'deferred-employee-address :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/6 + (with-dataset *ds-employees* + (every #'(lambda (ea) (slot-boundp (slot-value ea 'address) 'addressid)) + (select 'employee-address :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/7 + (with-dataset *ds-employees* + (every #'(lambda (ea) (slot-boundp (slot-value ea 'address) 'addressid)) + (select 'deferred-employee-address :flatp t :caching nil))) + t) + +(deftest :oodm/retrieval/8 + (with-dataset *ds-employees* + (mapcar #'(lambda (ea) (slot-value (slot-value ea 'address) 'street-number)) + (select 'employee-address :flatp t :order-by [aaddressid] :caching nil))) + (10 10 nil nil nil nil)) + +(deftest :oodm/retrieval/9 + (with-dataset *ds-employees* + (mapcar #'(lambda (ea) (slot-value (slot-value ea 'address) 'street-number)) + (select 'deferred-employee-address :flatp t :order-by [aaddressid] :caching nil))) + (10 10 nil nil nil nil)) + +(deftest :oodm/retrieval/10-slot-columns + (with-dataset *ds-employees* + (mapcar #'title + (select 'employee :flatp t :caching nil + :where [<= [emplid] 3] + :order-by `((,[emplid] :asc))))) + (supplicant :adherent cl-user::novice)) + +;; tests update-records-from-instance +(deftest :oodml/update-records/1 + (with-dataset *ds-employees* + (values + (progn + (let ((lenin (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil)))) + (format nil "~a ~a: ~a" + (first-name lenin) + (last-name lenin) + (employee-email lenin)))) + (progn + (setf (slot-value employee1 'first-name) "Dimitriy" + (slot-value employee1 'last-name) "Ivanovich" + (slot-value employee1 'email) "ivanovich@soviet.org") + (clsql:update-records-from-instance employee1) + (let ((lenin (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil)))) + (format nil "~a ~a: ~a" + (first-name lenin) + (last-name lenin) + (employee-email lenin)))))) + "Vladimir Lenin: lenin@soviet.org" + "Dimitriy Ivanovich: ivanovich@soviet.org") + +;; tests update-record-from-slot +(deftest :oodml/update-records/2 + (with-dataset *ds-employees* + ;(start-sql-recording :type :both) + (values + (employee-email + (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil))) + (progn + (setf (slot-value employee1 'email) "lenin-nospam@soviet.org") + (clsql:update-record-from-slot employee1 'email) + (employee-email + (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil)))))) + "lenin@soviet.org" "lenin-nospam@soviet.org") + +;; tests update-record-from-slots +(deftest :oodml/update-records/3 + (with-dataset *ds-employees* + (values + (let ((lenin (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil)))) + (format nil "~a ~a: ~a" + (first-name lenin) + (last-name lenin) + (employee-email lenin))) + (progn + (setf (slot-value employee1 'first-name) "Dimitriy" + (slot-value employee1 'last-name) "Ivanovich" + (slot-value employee1 'email) "ivanovich@soviet.org") + (clsql:update-record-from-slots employee1 '(first-name last-name email)) + (let ((lenin (car (clsql:select 'employee + :where [= 1 [slot-value 'employee 'emplid]] + :flatp t + :caching nil)))) + (format nil "~a ~a: ~a" + (first-name lenin) + (last-name lenin) + (employee-email lenin)))))) + "Vladimir Lenin: lenin@soviet.org" + "Dimitriy Ivanovich: ivanovich@soviet.org") + +(deftest :oodml/update-records/4 + (with-dataset *ds-nodes* + (flet ((print-fresh-node () + (let ((base (car (clsql:select 'node + :where [= 1 [slot-value 'node 'node-id]] + :flatp t :caching nil)))) + (format nil "~a ~a" + (slot-value base 'node-id) + (slot-value base 'title))))) + (values + (print-fresh-node) + (let ((base (car (clsql:select 'node + :where [= 1 [slot-value 'node 'node-id]] + :flatp t :caching nil)))) + (setf (slot-value base 'title) "Altered title") + (clsql:update-records-from-instance base) + (print-fresh-node))))) + "1 Bare node" + "1 Altered title") + +(deftest :oodml/update-records/4-slots ;just like 4, but use slots fns. + (with-dataset *ds-nodes* + (flet ((print-fresh-setting () + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (format nil "~a ~a ~a" + (slot-value node 'setting-id) + (slot-value node 'title) + (slot-value node 'vars))))) + (values + (print-fresh-setting) + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'vars) "Altered vars") + (clsql-sys:update-record-from-slot node 'title) + (clsql-sys:update-record-from-slot node 'vars) + (print-fresh-setting)) + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Setting2") + (setf (slot-value node 'vars) "var 2") + (clsql:update-records-from-instance node) + (clsql-sys:update-record-from-slots node '(vars title)) + (print-fresh-setting))))) + "3 Setting2 var 2" + "3 Altered title Altered vars" + "3 Setting2 var 2") + +(deftest :oodml/update-records/5 + (with-dataset *ds-nodes* + (flet ((print-fresh-setting () + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (format nil "~a ~a ~a" + (slot-value node 'setting-id) + (slot-value node 'title) + (slot-value node 'vars))))) + (values + (print-fresh-setting) + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'vars) "Altered vars") + (clsql:update-records-from-instance node) + (print-fresh-setting))))) + "3 Setting2 var 2" + "3 Altered title Altered vars") + +(deftest :oodml/update-records/5-slots + (with-dataset *ds-nodes* + (flet ((print-fresh-setting () + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (format nil "~a ~a ~a" + (slot-value node 'setting-id) + (slot-value node 'title) + (slot-value node 'vars))))) + (values + (print-fresh-setting) + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'vars) "Altered vars") + (clsql-sys:update-record-from-slot node 'title) + (clsql-sys:update-record-from-slot node 'vars) + (print-fresh-setting)) + (let ((node (car (clsql:select 'setting + :where [= 3 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Setting2") + (setf (slot-value node 'vars) "var 2") + (clsql-sys:update-record-from-slots node '(title vars)) + (print-fresh-setting))))) + "3 Setting2 var 2" + "3 Altered title Altered vars" + "3 Setting2 var 2") + +(deftest :oodml/update-records/6 + (with-dataset *ds-nodes* + (flet ((print-fresh-setting () + (let ((node (car (clsql:select 'setting + :where [= 7 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (format nil "~a ~a ~a" + (slot-value node 'setting-id) + (slot-value node 'title) + (or (slot-value node 'vars) "NIL"))))) + (values + (print-fresh-setting) + (let ((node (car (clsql:select 'setting + :where [= 7 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'vars) "Altered vars") + (clsql:update-records-from-instance node) + (print-fresh-setting)) + (let ((node (car (clsql:select 'setting + :where [= 7 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "theme-2") + (setf (slot-value node 'vars) nil) + (clsql:update-records-from-instance node) + (print-fresh-setting))))) + "7 theme-2 NIL" + "7 Altered title Altered vars" + "7 theme-2 NIL") + +(deftest :oodml/update-records/7 + (with-dataset *ds-nodes* + (flet ((print-fresh-user () + "requery to get what the db has, and print out." + (let ((node (car (clsql:select 'user + :where [= 5 [slot-value 'user 'user-id]] + :flatp t :caching nil)))) + (format nil "~a ~a ~a" + (slot-value node 'user-id) + (slot-value node 'title) + (slot-value node 'nick))))) + (values + (print-fresh-user) + (let ((node (car (clsql:select 'user + :where [= 5 [slot-value 'user 'user-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'nick) "Altered nick") + (clsql:update-records-from-instance node) + (print-fresh-user))))) + "5 user-2 second user" + "5 Altered title Altered nick") + +(deftest :oodml/update-records/8 + (with-dataset *ds-nodes* + (flet ((print-fresh-theme () + (let ((node (car (clsql:select 'theme + :where [= 6 [slot-value 'theme 'theme-id]] + :flatp t :caching nil)))) + (with-slots (node-id setting-id theme-id title vars doc) node + (format nil "~a ~a ~a ~a ~a ~a" + node-id setting-id theme-id + title (or vars "NIL") doc))))) + (values + (print-fresh-theme) + (let ((node (car (clsql:select 'setting + :where [= 6 [slot-value 'setting 'setting-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title") + (setf (slot-value node 'vars) nil) + (clsql:update-records-from-instance node) + (print-fresh-theme)) + (let ((node (car (clsql:select 'theme + :where [= 6 [slot-value 'theme 'theme-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "Altered title again") + (setf (slot-value node 'doc) "altered doc") + (clsql:update-records-from-instance node) + (print-fresh-theme)) + (let ((node (car (clsql:select 'theme + :where [= 6 [slot-value 'theme 'theme-id]] + :flatp t :caching nil)))) + (setf (slot-value node 'title) "theme-1") + (setf (slot-value node 'vars) "empty") + (setf (slot-value node 'doc) "first theme") + (clsql:update-records-from-instance node) + (print-fresh-theme))))) + "6 6 6 theme-1 empty first theme" + "6 6 6 Altered title NIL first theme" + "6 6 6 Altered title again NIL altered doc" + "6 6 6 theme-1 empty first theme") + +(deftest :oodml/update-records/9 + (with-dataset *ds-nodes* + (flet ((print-fresh-subloc () + (let ((sl (car (clsql:select 'subloc + :where [= 10 [slot-value 'subloc 'subloc-id]] + :flatp t :caching nil)))) + (unless sl + (error "Couldn't find expected sublocation")) + (format nil "~a ~a ~a" + (slot-value sl 'subloc-id) + (slot-value sl 'title) + (slot-value sl 'loc))))) + (values + (print-fresh-subloc) + (let ((sl (car (clsql:select 'subloc + :where [= 10 [slot-value 'subloc 'subloc-id]] + :flatp t :caching nil)))) + (setf (slot-value sl 'title) "Altered subloc title") + (setf (slot-value sl 'loc) "Altered loc") + (clsql:update-records-from-instance sl) + (print-fresh-subloc))))) + "10 subloc-1 a subloc" + "10 Altered subloc title Altered loc") + +(deftest :oodml/update-records/9-slots ;like 9, but use slots fns. + (with-dataset *ds-nodes* + (flet ((print-fresh-subloc () + (let ((sl (car (clsql:select 'subloc + :where [= 10 [slot-value 'subloc 'subloc-id]] + :flatp t :caching nil)))) + (unless sl + (error "In psfl: found no sublocation with id = 10")) + (format nil "~a ~a ~a" + (slot-value sl 'subloc-id) + (slot-value sl 'title) + (slot-value sl 'loc))))) + (values + (print-fresh-subloc) + (let ((sl (car (clsql:select 'subloc + :where [= 10 [slot-value 'subloc 'subloc-id]] + :flatp t :caching nil)))) + (unless sl + (error "Select for modification: Found no sublocation with id = 10")) + (setf (slot-value sl 'title) "Altered subloc title") + (setf (slot-value sl 'loc) "Altered loc") + (clsql:update-record-from-slot sl 'title) + (clsql:update-record-from-slot sl 'loc) + (print-fresh-subloc)) + (let ((sl (car (clsql:select 'subloc + :where [= 10 [slot-value 'subloc 'subloc-id]] + :flatp t :caching nil)))) + (unless sl + (error "Select for next modification: Found no sublocation with id = 10")) + (setf (slot-value sl 'title) "subloc-1") + (setf (slot-value sl 'loc) "a subloc") + (clsql:update-record-from-slots sl '(title loc)) + (print-fresh-subloc))))) + "10 subloc-1 a subloc" + "10 Altered subloc title Altered loc" + "10 subloc-1 a subloc") + +;; Verify that we can set a float to nil and then read it back +;; (was failing in Postgresql at somepoint) +(deftest :oodml/update-records/10 + (with-dataset *ds-employees* + (let ((emp (first (clsql:select 'employee :where [= [emplid] 1] :flatp t)))) + (setf (height emp) nil) + (clsql-sys:update-record-from-slot emp 'height) + (values + (clsql:select [height] :from [employee] :where [= [emplid] 1]) + (progn + (setf (height emp) 42.0) + (clsql-sys:update-record-from-slot emp 'height) + (clsql:select [height] :from [employee] :where [= [emplid] 1])) + (progn + (setf (height emp) 24.13d0) + (clsql-sys:update-record-from-slot emp 'height) + (clsql:select [height] :from [employee] :where [= [emplid] 1]))))) + ((nil)) + ((42.0d0)) + ((24.13d0))) + +(deftest :oodml/update-records/11 + (with-dataset *ds-artists* + (clsql:update-records-from-instance artist1) + (list (name artist1) (artist_id artist1))) + ("Mogwai" 1)) + +(deftest :oodml/update-records/12 + (with-dataset *ds-artists* + (clsql:update-records-from-instance artist1) + (list (name artist1) (genre artist1))) + ("Mogwai" "Unknown")) + +;; tests update-instance-from-records +(deftest :oodml/update-instance/1 + (with-dataset *ds-employees* + (values + (format nil "~a ~a: ~a" + (slot-value employee1 'first-name) + (slot-value employee1 'last-name) + (slot-value employee1 'email)) + (progn + (clsql:update-records [employee] + :av-pairs '(([first-name] "Ivan") + ([last-name] "Petrov") + ([email] "petrov@soviet.org")) + :where [= [emplid] 1]) + (clsql:update-instance-from-records employee1) + (format nil "~a ~a: ~a" + (slot-value employee1 'first-name) + (slot-value employee1 'last-name) + (slot-value employee1 'email))))) + "Vladimir Lenin: lenin@soviet.org" + "Ivan Petrov: petrov@soviet.org") + +;; tests update-slot-from-record +(deftest :oodml/update-instance/2 + (with-dataset *ds-employees* + (values + (slot-value employee1 'email) + (progn + (clsql:update-records [employee] + :av-pairs '(([email] "lenin-nospam@soviet.org")) + :where [= [emplid] 1]) + (clsql:update-slot-from-record employee1 'email) + (slot-value employee1 'email)))) + "lenin@soviet.org" "lenin-nospam@soviet.org") + +;; tests normalizedp update-instance-from-records +(deftest :oodml/update-instance/3 + (with-dataset *ds-nodes* + (values + (with-output-to-string (out) + (format out "~a ~a ~a ~a" + (slot-value theme2 'theme-id) + (slot-value theme2 'title) + (or (slot-value theme2 'vars) "NIL") + (slot-value theme2 'doc))) + (progn + (clsql:update-records [node] :av-pairs '(([title] "Altered title")) + :where [= [node-id] 7]) + (clsql:update-records [setting] :av-pairs '(([vars] "Altered vars")) + :where [= [setting-id] 7]) + (clsql:update-records [theme] :av-pairs '(([doc] "Altered doc")) + :where [= [theme-id] 7]) + (clsql:update-instance-from-records theme2) + (with-output-to-string (out) + (format out "~a ~a ~a ~a" + (slot-value theme2 'theme-id) + (slot-value theme2 'title) + (slot-value theme2 'vars) + (slot-value theme2 'doc)))))) + "7 theme-2 NIL second theme" + "7 Altered title Altered vars Altered doc") + +(deftest :oodml/update-instance/4 + (with-dataset *ds-nodes* + (values + (progn + (setf loc2 (car (clsql:select 'location + :where [= [node-id] 9] + :flatp t :caching nil))) + (format nil "~a ~a" + (slot-value loc2 'node-id) + (slot-value loc2 'title))) + (progn + (clsql:update-records [node] :av-pairs '(([title] "Altered title")) + :where [= [node-id] 9]) + (clsql:update-instance-from-records loc2) + (format nil "~a ~a" + (slot-value loc2 'node-id) + (slot-value loc2 'title))))) + "9 location-2" + "9 Altered title") + +(deftest :oodml/update-instance/5 + (with-dataset *ds-nodes* + (values + (format nil "~a ~a ~a" + (slot-value subloc2 'subloc-id) + (slot-value subloc2 'title) + (slot-value subloc2 'loc)) + (progn + (clsql:update-records [node] :av-pairs '(([title] "Altered title")) + :where [= [node-id] 11]) + (clsql:update-records [subloc] :av-pairs '(([loc] "Altered loc")) + :where [= [subloc-id] 11]) + (clsql:update-instance-from-records subloc2) + (format nil "~a ~a ~a" + (slot-value subloc2 'subloc-id) + (slot-value subloc2 'title) + (slot-value subloc2 'loc))))) + "11 subloc-2 second subloc" + "11 Altered title Altered loc") + +;; tests update-slot-from-record with normalizedp stuff +(deftest :oodml/update-instance/6 + (with-dataset *ds-nodes* + (values + (slot-value theme1 'doc) + (slot-value theme1 'vars) + (progn + (clsql:update-records [theme] + :av-pairs '(([doc] "altered doc")) + :where [= [theme-id] 6]) + (clsql:update-slot-from-record theme1 'doc) + (slot-value theme1 'doc)) + (progn + (clsql:update-records [setting] + :av-pairs '(([vars] "altered vars")) + :where [= [setting-id] 6]) + (clsql:update-slot-from-record theme1 'vars) + (slot-value theme1 'vars)))) + "first theme" "empty" + "altered doc" "altered vars") + +(deftest :oodml/update-instance/7 + (flet ((print-loc (l) + (format nil "~a: ~a" + (slot-value l 'node-id) (slot-value l 'title))) + (print-subloc (sl) + (format nil "~a: ~a" + (slot-value sl 'node-id) (slot-value sl 'loc)))) + (with-dataset *ds-nodes* + (values + (print-loc loc2) + (print-subloc subloc2) + (progn + (clsql:update-records [node] + :av-pairs '(([title] "altered title")) + :where [= [node-id] (node-id loc2)]) + (clsql:update-slot-from-record loc2 'title) + (print-loc loc2)) + (progn + (clsql:update-records [subloc] + :av-pairs '(([loc] "altered loc")) + :where [= [subloc-id] (subloc-id subloc2)]) + (clsql:update-slot-from-record subloc2 'loc) + (print-subloc subloc2))))) + "9: location-2" "11: second subloc" + "9: altered title" "11: altered loc") + +(deftest :oodml/do-query/1 + (with-dataset *ds-employees* + (let ((result '())) + (clsql:do-query ((e) [select 'employee :order-by [emplid]]) + (push (slot-value e 'last-name) result)) + result)) + ("Putin" "Yeltsin" "Gorbachev" "Chernenko" "Andropov" "Brezhnev" "Kruschev" + "Trotsky" "Stalin" "Lenin")) + +(deftest :oodml/do-query/2 + (with-dataset *ds-employees* + (let ((result '())) + (clsql:do-query ((e c) [select 'employee 'company + :where [= [slot-value 'employee 'last-name] + "Lenin"]]) + (push (list (slot-value e 'last-name) (slot-value c 'name)) + result)) + result)) + (("Lenin" "Widgets Inc."))) + +(deftest :oodml/map-query/1 + (with-dataset *ds-employees* + (clsql:map-query 'list #'last-name [select 'employee :order-by [emplid]])) + ("Lenin" "Stalin" "Trotsky" "Kruschev" "Brezhnev" "Andropov" "Chernenko" + "Gorbachev" "Yeltsin" "Putin")) + +(deftest :oodml/map-query/2 + (with-dataset *ds-employees* + (clsql:map-query 'list #'(lambda (e c) (list (slot-value e 'last-name) + (slot-value c 'name))) + [select 'employee 'company :where [= [slot-value 'employee 'last-name] + "Lenin"]])) + (("Lenin" "Widgets Inc."))) + +(deftest :oodml/iteration/3 + (with-dataset *ds-employees* + (loop for (e) being the records in + [select 'employee :where [< [emplid] 4] :order-by [emplid]] + collect (slot-value e 'last-name))) + ("Lenin" "Stalin" "Trotsky")) + + +(deftest :oodml/cache/1 + (with-dataset *ds-employees* + (let ((*default-caching* t)) + (setf (clsql-sys:record-caches *default-database*) nil) + (let ((employees (select 'employee))) + (every #'(lambda (a b) (eq a b)) + employees (select 'employee))))) + t) + +(deftest :oodml/cache/2 + (with-dataset *ds-employees* + (let* ((*default-caching* t) + (employees (select 'employee))) + (equal employees (select 'employee :flatp t)))) + nil) + +(deftest :oodml/refresh/1 + (with-dataset *ds-employees* + (let* ((clsql-sys:*default-caching* t) + (addresses (select 'address))) + (equal addresses (select 'address :refresh t)))) + t) + +(deftest :oodml/refresh/2 + (with-dataset *ds-employees* + (let* ((clsql-sys:*default-caching* t) + (addresses (select 'address :order-by [addressid] :flatp t :refresh t)) + (city (slot-value (car addresses) 'city))) + (clsql:update-records [addr] + :av-pairs '((city_field "A new city")) + :where [= [addressid] (slot-value (car addresses) 'addressid)]) + (let* ((new-addresses (select 'address :order-by [addressid] :refresh t :flatp t)) + (new-city (slot-value (car addresses) 'city)) + ) + (clsql:update-records [addr] + :av-pairs `((city_field ,city)) + :where [= [addressid] (slot-value (car addresses) 'addressid)]) + (values (equal addresses new-addresses) + city + new-city)))) + t "Leningrad" "A new city") + +(deftest :oodml/refresh/3 + (with-dataset *ds-employees* + (let* ((clsql-sys:*default-caching* t) + (addresses (select 'address :order-by [addressid] :flatp t))) + (values + (equal addresses (select 'address :refresh t :flatp t)) + (equal addresses (select 'address :flatp t))))) + nil nil) + +(deftest :oodml/refresh/4 + (with-dataset *ds-employees* + (let* ((clsql-sys:*default-caching* t) + (addresses (select 'address :order-by [addressid] :flatp t :refresh t)) + (*db-auto-sync* t)) + (make-instance 'address :addressid 1000 :city "A new address city") + (let ((new-addresses (select 'address :order-by [addressid] :flatp t :refresh t))) + (values + (length addresses) + (length new-addresses) + (eq (first addresses) (first new-addresses)) + (eq (second addresses) (second new-addresses)))))) + 3 4 t t) + + +(deftest :oodml/uoj/full-set + (with-dataset *ds-employees* + (progn + (let* ((dea-list (select 'deferred-employee-address + :caching nil :order-by ["ea_join" aaddressid] + :flatp t)) + (dea-list-copy (copy-seq dea-list)) + (initially-unbound (every #'(lambda (dea) (not (slot-boundp dea 'address))) dea-list))) + (update-objects-joins dea-list :slots 'address :max-len nil) + (values + initially-unbound + (equal dea-list dea-list-copy) + (every #'(lambda (dea) (slot-boundp dea 'address)) dea-list) + (every #'(lambda (dea) (typep (slot-value dea 'address) 'address)) dea-list) + (mapcar #'(lambda (dea) (slot-value (slot-value dea 'address) 'addressid)) dea-list))))) + t t t t (1 1 2 2 2 3)) + +(deftest :oodml/uoj/batched + (with-dataset *ds-employees* + (progn + (let* ((dea-list (select 'deferred-employee-address + :caching nil :order-by ["ea_join" aaddressid] + :flatp t)) + (dea-list-copy (copy-seq dea-list)) + (initially-unbound (every #'(lambda (dea) (not (slot-boundp dea 'address))) dea-list))) + (update-objects-joins dea-list :slots 'address :max-len 2) + (values + initially-unbound + (equal dea-list dea-list-copy) + (every #'(lambda (dea) (slot-boundp dea 'address)) dea-list) + (every #'(lambda (dea) (typep (slot-value dea 'address) 'address)) dea-list) + (mapcar #'(lambda (dea) (slot-value (slot-value dea 'address) 'addressid)) dea-list))))) + t t t t (1 1 2 2 2 3)) + +;; update-object-joins needs to be fixed for multiple keys +#+ignore +(deftest :oodml/uoj/multi-key + (progn + (clsql:update-objects-joins (list company1)) + (mapcar #'(lambda (e) + (slot-value e 'ecompanyid)) + (company-employees company1))) + (1 1 1 1 1 1 1 1 1 1)) + +(deftest :oodml/big/1 + (with-dataset *ds-big* + (let ((objs (clsql:select 'big :order-by [i] :flatp t))) + (values + (length objs) + (do ((i 0 (1+ i)) + (max (expt 2 60)) + (rest objs (cdr rest))) + ((= i (length objs)) t) + (let ((obj (car rest)) + (index (1+ i))) + (unless (and (eql (slot-value obj 'i) index) + (eql (slot-value obj 'bi) (truncate max index))) + (print index) + (describe obj) + (return nil))))))) + 555 t) + +(deftest :oodml/db-auto-sync/1 + (with-dataset *ds-employees* + (values + (progn + (make-instance 'employee :emplid 20 :groupid 1 + :last-name "Ivanovich") + (select [last-name] :from [employee] :where [= [emplid] 20] + :flatp t :field-names nil)) + (let ((*db-auto-sync* t)) + (make-instance 'employee :emplid 20 :groupid 1 + :last-name "Ivanovich") + (prog1 (select [last-name] :from [employee] :flatp t + :field-names nil + :where [= [emplid] 20]) + (delete-records :from [employee] :where [= [emplid] 20]))))) + nil ("Ivanovich")) + +(deftest :oodml/db-auto-sync/2 + (with-dataset *ds-employees* + (values + (let ((instance (make-instance 'employee :emplid 20 :groupid 1 + :last-name "Ivanovich"))) + (setf (slot-value instance 'last-name) "Bulgakov") + (select [last-name] :from [employee] :where [= [emplid] 20] + :flatp t :field-names nil)) + (let* ((*db-auto-sync* t) + (instance (make-instance 'employee :emplid 20 :groupid 1 + :last-name "Ivanovich"))) + (setf (slot-value instance 'last-name) "Bulgakov") + (prog1 (select [last-name] :from [employee] :flatp t + :field-names nil + :where [= [emplid] 20]) + (delete-records :from [employee] :where [= [emplid] 20]))))) + nil ("Bulgakov")) + +(deftest :oodml/db-auto-sync/3 + (with-dataset *ds-nodes* + (values + (progn + (make-instance 'theme :title "test-theme" :vars "test-vars" + :doc "test-doc") + (select [node-id] :from [node] :where [= [title] "test-theme"] + :flatp t :field-names nil)) + (let ((*db-auto-sync* t)) + (make-instance 'theme :title "test-theme" :vars "test-vars" + :doc "test-doc") + (prog1 (select [title] :from [node] :where [= [title] "test-theme"] + :flatp t :field-names nil) + (delete-records :from [node] :where [= [title] "test-theme"]) + (delete-records :from [setting] :where [= [vars] "test-vars"]) + (delete-records :from [theme] :where [= [doc] "test-doc"]))))) + nil ("test-theme")) + +(deftest :oodml/db-auto-sync/4 + (with-dataset *ds-nodes* + (values + (let ((inst (make-instance 'theme + :title "test-theme" :vars "test-vars" + :doc "test-doc")) + (*print-circle* nil)) + (setf (slot-value inst 'title) "alternate-test-theme") + (format nil "~a ~a ~a ~a" + (or (select [title] :from [node] + :where [= [title] "test-theme"] + :flatp t :field-names nil) "NIL") + (or (select [vars] :from [setting] + :where [= [vars] "test-vars"] + :flatp t :field-names nil) "NIL") + (or (select [doc] :from [theme] + :where [= [doc] "test-doc"] + :flatp t :field-names nil) "NIL") + (or (select [title] :from [node] + :where [= [title] "alternate-test-theme"] + :flatp t :field-names nil) "NIL"))) + (let* ((*db-auto-sync* t) + (inst (make-instance 'theme + :title "test-theme" :vars "test-vars" + :doc "test-doc"))) + (setf (slot-value inst 'title) "alternate-test-theme") + (prog1 + (format nil "~a ~a ~a ~a" + (or (select [title] :from [node] + :where [= [title] "test-theme"] + :flatp t :field-names nil) "NIL") + (or (select [vars] :from [setting] + :where [= [vars] "test-vars"] + :flatp t :field-names nil) "NIL") + (or (select [doc] :from [theme] + :where [= [doc] "test-doc"] + :flatp t :field-names nil) "NIL") + (or (select [title] :from [node] + :where [= [title] "alternate-test-theme"] + :flatp t :field-names nil) "NIL")) + (delete-records :from [node] :where [= [title] "alternate-test-theme"]) + (delete-records :from [setting] :where [= [vars] "test-vars"]) + (delete-records :from [theme] :where [= [doc] "test-doc"]))))) + "NIL NIL NIL NIL" + "NIL (test-vars) (test-doc) (alternate-test-theme)") + +(deftest :oodml/setf-slot-value/1 + (with-dataset *ds-employees* + (let* ((*db-auto-sync* t) + (instance (make-instance 'employee :emplid 20 :groupid 1))) + (prog1 + (setf + (slot-value instance 'first-name) "Mikhail" + (slot-value instance 'last-name) "Bulgakov") + (delete-records :from [employee] :where [= [emplid] 20])))) + "Bulgakov") + +(deftest :oodml/float/1 + (with-dataset *ds-employees* + (let* ((emp1 (car (select 'employee + :where [= [slot-value 'employee 'emplid] + 1] + :flatp t + :caching nil))) + (height (slot-value emp1 'height))) + (prog1 + (progn + (setf (slot-value emp1 'height) 1.0E0) + (clsql:update-record-from-slot emp1 'height) + (= (car (clsql:select [height] :from [employee] + :where [= [emplid] 1] + :flatp t + :field-names nil)) + 1)) + (setf (slot-value emp1 'height) height) + (clsql:update-record-from-slot emp1 'height)))) + t) + +(deftest :oodml/float/2 + (with-dataset *ds-employees* + (let* ((emp1 (car (select 'employee + :where [= [slot-value 'employee 'emplid] + 1] + :flatp t + :caching nil))) + (height (slot-value emp1 'height))) + (prog1 + (progn + (setf (slot-value emp1 'height) 1.0S0) + (clsql:update-record-from-slot emp1 'height) + (= (car (clsql:select [height] :from [employee] + :where [= [emplid] 1] + :flatp t + :field-names nil)) + 1)) + (setf (slot-value emp1 'height) height) + (clsql:update-record-from-slot emp1 'height)))) + t) + +(deftest :oodml/float/3 + (with-dataset *ds-employees* + (let* ((emp1 (car (select 'employee + :where [= [slot-value 'employee 'emplid] + 1] + :flatp t + :caching nil))) + (height (slot-value emp1 'height))) + (prog1 + (progn + (setf (slot-value emp1 'height) 1.0F0) + (clsql:update-record-from-slot emp1 'height) + (= (car (clsql:select [height] :from [employee] + :where [= [emplid] 1] + :flatp t + :field-names nil)) + 1)) + (setf (slot-value emp1 'height) height) + (clsql:update-record-from-slot emp1 'height)))) + t) + +(deftest :oodml/float/4 + (with-dataset *ds-employees* + (let* ((emp1 (car (select 'employee + :where [= [slot-value 'employee 'emplid] + 1] + :flatp t + :caching nil))) + (height (slot-value emp1 'height))) + (prog1 + (progn + (setf (slot-value emp1 'height) 1.0D0) + (clsql:update-record-from-slot emp1 'height) + (= (car (clsql:select [height] :from [employee] + :where [= [emplid] 1] + :flatp t + :field-names nil)) + 1)) + (setf (slot-value emp1 'height) height) + (clsql:update-record-from-slot emp1 'height)))) + t) + +(deftest :oodml/float/5 + (with-dataset *ds-employees* + (let* ((emp1 (car (select 'employee + :where [= [slot-value 'employee 'emplid] + 1] + :flatp t + :caching nil))) + (height (slot-value emp1 'height))) + (prog1 + (progn + (setf (slot-value emp1 'height) 1.0L0) + (clsql:update-record-from-slot emp1 'height) + (= (car (clsql:select [height] :from [employee] + :where [= [emplid] 1] + :flatp t + :field-names nil)) + 1)) + (setf (slot-value emp1 'height) height) + (clsql:update-record-from-slot emp1 'height)))) + t) +)) + + diff --git a/tests/test-pool.lisp b/tests/test-pool.lisp new file mode 100644 index 0000000..ef0215b --- /dev/null +++ b/tests/test-pool.lisp @@ -0,0 +1,83 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: test-pool.lisp +;;;; Purpose: Tests for connection pools +;;;; Author: Ryan Davis +;;;; Created: June 27 2011 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2004-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* +(in-package #:clsql-tests) + +;; setup a dummy database for the pool to use +(pushnew :dummy clsql-sys:*loaded-database-types*) +(defclass dummy-database (clsql-sys:database) () + (:default-initargs :database-type :dummy)) +(defmethod clsql-sys:database-connect (connection-spec (database-type (eql :dummy))) + (let ((db (make-instance 'dummy-database :connection-spec connection-spec))) + (setf (slot-value db 'clsql-sys::state) :open) + db)) +(defmethod clsql-sys::database-name-from-spec (connection-spec (database-type (eql :dummy))) + "dummy") +(defmethod clsql-sys::database-acquire-from-conn-pool ((db dummy-database)) T) + +(setq *rt-pool* + '( + (deftest :pool/acquire + (let ((pool (clsql-sys::find-or-create-connection-pool nil :dummy)) + dbx res) + (clsql-sys::clear-conn-pool pool) + (flet ((test-result (x) (push x res))) + (test-result (length (clsql-sys::all-connections pool))) + (test-result (length (clsql-sys::free-connections pool))) + + (clsql-sys:with-database (db nil :database-type :dummy :pool T) + (test-result (not (null db))) + (test-result (length (clsql-sys::all-connections pool))) + (test-result (length (clsql-sys::free-connections pool))) + (setf dbx db)) + (test-result (length (clsql-sys::all-connections pool))) + (test-result (length (clsql-sys::free-connections pool))) + (clsql-sys:with-database (db nil :database-type :dummy :pool T) + (test-result (eq db dbx))) + ) + (nreverse res)) + (0 0 T 1 0 1 1 T) + ) + + (deftest :pool/max-free-connections + (let ((pool (clsql-sys::find-or-create-connection-pool nil :dummy))) + (flet ((run (max-free dbs-to-release) + (let ((clsql-sys:*db-pool-max-free-connections* max-free) + dbs) + (clsql-sys::clear-conn-pool pool) + (dotimes (i dbs-to-release dbs) + (push (clsql-sys:connect nil :database-type :dummy + :pool T :if-exists :new) + dbs)) + (list (length (clsql-sys::all-connections pool)) + (progn + (dolist (db dbs) (clsql-sys:disconnect :database db)) + (length (clsql-sys::free-connections pool)) + ))))) + (append + (run 5 10) + (run nil 10)))) + (10 5 10 10) + ) + + + + (deftest :pool/find-or-create-connection-pool + (let ((p (clsql-sys::find-or-create-connection-pool nil :dummy))) + (values (null p) + (eq p (clsql-sys::find-or-create-connection-pool nil :dummy)) + (eq p (clsql-sys::find-or-create-connection-pool :spec :dummy)))) + nil T nil) + )) diff --git a/tests/test-syntax.lisp b/tests/test-syntax.lisp new file mode 100644 index 0000000..01a7612 --- /dev/null +++ b/tests/test-syntax.lisp @@ -0,0 +1,465 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql.asd +;;;; Purpose: Tests for the CLSQL Symbolic SQL syntax. +;;;; Authors: Marcus Pearce and Kevin M. Rosenberg +;;;; Created: March 2004 +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + +(clsql-sys:file-enable-sql-reader-syntax) + + +(setq *rt-syntax* + '( + +(deftest :syntax/generic/1 + (clsql:sql "foo") + "'foo'") + +(deftest :syntax/generic/2 + (clsql:sql 23) + "23") + +(deftest :syntax/generic/3 + (clsql:sql 'bar) + "BAR") + +(deftest :syntax/generic/4 + (clsql:sql '("ten" 10 ten)) + "('ten',10,TEN)") + +(deftest :syntax/generic/5 + (clsql:sql ["SELECT FOO,BAR FROM BAZ"]) + "SELECT FOO,BAR FROM BAZ") + +(deftest :syntax/generic/6 + (clsql:sql "What's up Doc?") + "'What''s up Doc?'") + +(deftest :syntax/ident/1 + (clsql:sql [foo]) + "FOO") + +(deftest :syntax/ident/2 + (clsql:sql [foo bar]) + "FOO.BAR") + +(deftest :syntax/ident/3 + (clsql:sql [foo :integer]) + "FOO") + +(deftest :syntax/ident/4 + (clsql:sql [foo bar :integer]) + "FOO.BAR") + +(deftest :syntax/ident/5 + (clsql:sql [foo "bar"]) + "FOO \"bar\"") + +(deftest :syntax/ident/6 + (clsql:sql ["foo" bar]) + "\"foo\".BAR") + +(deftest :syntax/ident/7 + (clsql:sql ["foo" bar :integer]) + "\"foo\".BAR") + + +(deftest :syntax/attribute/1 + (clsql:sql (clsql:sql-expression :table 'foo :attribute 'bar)) + "FOO.BAR") + +(deftest :syntax/attribute/2 + (clsql:sql (clsql:sql-expression :table 'foo :attribute "bar")) + "FOO.\"bar\"") + +(deftest :syntax/attribute/3 + (clsql:sql (clsql:sql-expression :table "foo" :attribute 'bar)) + "\"foo\".BAR") + +(deftest :syntax/attribute/4 + (clsql:sql (clsql:sql-expression :table "foo" :attribute "bar")) + "\"foo\".\"bar\"") + + +(deftest :syntax/subquery/1 + (clsql:sql [any '(3 4)]) + "ANY((3,4))") + +(deftest :syntax/subquery/2 + (clsql:sql [in [foo] '(foo bar baz)]) + "(FOO IN (FOO,BAR,BAZ))") + +(deftest :syntax/subquery/3 + (clsql:sql [all '(foo bar baz)]) + "ALL((FOO,BAR,BAZ))") + +(deftest :syntax/subquery/4 + (clsql:sql [exists '(foo bar baz)]) + "EXISTS((FOO,BAR,BAZ))") + +(deftest :syntax/subquery/5 + (clsql:sql [some '(foo bar baz)]) + "SOME((FOO,BAR,BAZ))") + + +(deftest :syntax/aggregate/1 + (clsql:sql [max [+ [foo] [* 1000 [bar]]]]) + "MAX((FOO + (1000 * BAR)))") + +(deftest :syntax/aggregate/2 + (clsql:sql [avg [+ [foo] [* 1000 [bar]]]]) + "AVG((FOO + (1000 * BAR)))") + +(deftest :syntax/aggregate/3 + (clsql:sql [min [+ [foo] [* 1000 [bar]]]]) + "MIN((FOO + (1000 * BAR)))") + +(deftest :syntax/aggregate/4 + (clsql:sql [sum [foo] [bar]]) + "SUM(FOO,BAR)") + +(deftest :syntax/aggregate/5 + (clsql:sql [count [foo]]) + "COUNT(FOO)") + + +(deftest :syntax/logical/1 + (values (clsql:sql [and [foo] [bar]]) + (clsql:sql [or [foo] [bar]])) + "(FOO AND BAR)" + "(FOO OR BAR)") + +(deftest :syntax/logical/2 + (clsql:sql [not [foo]]) + "(NOT (FOO))") + +;;; Test how we apply logical operators when we have different numbers of children +;;; This is useful if we wish to (apply #'sql-and some-list) without having to do +;;; alot of length checking +(deftest :syntax/logical/3 + (values (clsql:sql [and ]) + (clsql:sql [and [foo]]) + (clsql:sql [and [not [foo]]]) + (clsql:sql [and [foo] [bar] [baz]])) + "" + "FOO" + "(NOT (FOO))" + "(FOO AND BAR AND BAZ)") + +(deftest :syntax/logical/4 + (clsql:sql [and [= [foo] [bar]]]) + "(FOO = BAR)") + +(deftest :syntax/logical/5 + (clsql:sql [and [= [foo] [bar]] + [= [bar] [bast]] + [= [block] [blech]]]) + "((FOO = BAR) AND (BAR = BAST) AND (BLOCK = BLECH))") + +(deftest :syntax/logical/6 + (clsql:sql + (apply #'sql-and + (list [= [foo] [bar]] + [and ] + [and [= [bar] [bast]]]))) + "((FOO = BAR) AND (BAR = BAST))") + + +(deftest :syntax/null/1 + (clsql:sql [null [foo]]) + "(FOO IS NULL)") + +(deftest :syntax/null/2 + (clsql:sql [not [null [foo]]]) + "(NOT ((FOO IS NULL)))") + +(deftest :syntax/null/3 + (clsql:sql [null]) + "NULL") + +(deftest :syntax/null/4 + (clsql:sql [not [null]]) + "(NOT (NULL))") + +(deftest :syntax/null/5 + (clsql:sql [is [foo.bar] [null]]) + "(FOO.BAR IS NULL)") + +(deftest :syntax/null/6 + (clsql:sql [is [foo.bar] [not-null]]) + "(FOO.BAR IS NOT NULL)") + +(deftest :syntax/null/7 + (clsql:sql [not-null [foo.bar]]) + "(FOO.BAR IS NOT NULL)") + + + +(deftest :syntax/relational/1 + (clsql:sql [> [baz] [beep]]) + "(BAZ > BEEP)") + +(deftest :syntax/relational/2 + (let ((x 10)) + (clsql:sql [> [foo] x])) + "(FOO > 10)") + +(deftest :syntax/relational/3 + (clsql:sql [>= [baz] [beep]]) + "(BAZ >= BEEP)") + +(deftest :syntax/relational/4 + (clsql:sql [< [baz] [beep]]) + "(BAZ < BEEP)") + +(deftest :syntax/relational/5 + (clsql:sql [= [baz] [beep]]) + "(BAZ = BEEP)") + +(deftest :syntax/relational/6 + (clsql:sql [<> [baz] [beep]]) + "(BAZ <> BEEP)") + + +(deftest :syntax/between/1 + (clsql:sql [between [- [foo] 1] [* [bar] 5] [/ [baz] 9]]) + "(FOO - 1) BETWEEN (BAR * 5) AND (BAZ / 9)") + +(deftest :syntax/between/2 + (clsql:sql [not [between [- [foo] 1] [* [bar] 5] [/ [baz] 9]]]) + "(NOT ((FOO - 1) BETWEEN (BAR * 5) AND (BAZ / 9)))") + + +(deftest :syntax/arithmetic/1 + (clsql:sql [+ [foo bar] [baz]]) + "(FOO.BAR + BAZ)") + +(deftest :syntax/arithmetic/2 + (clsql:sql [- [foo bar] [baz]]) + "(FOO.BAR - BAZ)") + +(deftest :syntax/arithmetic/3 + (clsql:sql [/ [foo bar] [baz]]) + "(FOO.BAR / BAZ)") + +(deftest :syntax/arithmetic/4 + (clsql:sql [* [foo bar] [baz]]) + "(FOO.BAR * BAZ)") + +(deftest :syntax/arithmetic/5 + (clsql:sql [- [foo bar]]) + "(- (FOO.BAR))") + +(deftest :syntax/arithmetic/6 + (clsql:sql [* 2 3]) + "(2 * 3)") + + +(deftest :syntax/substr/1 + (clsql:sql [substr [hello] 1 4]) + "SUBSTR(HELLO,1,4)") + +(deftest :syntax/substring/1 + (clsql:sql [substring [hello] 1 4]) + "SUBSTRING(HELLO,1,4)") + + +(deftest :syntax/concat/1 + (clsql:sql [|| [foo] [bar] [baz]]) + "(FOO || BAR || BAZ)") + +(deftest :syntax/concat/2 + (clsql:sql [concat [foo] [bar]]) + "CONCAT(FOO,BAR)") + + +(deftest :syntax/pattern/1 + (clsql:sql [like [foo] "%v"]) + "(FOO LIKE '%v')") + +(deftest :syntax/pattern/2 + (clsql:sql [not [like [foo] "%v"]]) + "(NOT ((FOO LIKE '%v')))") + + +(deftest :syntax/distinct/1 + (clsql:sql [distinct [foo bar :string]]) + "DISTINCT FOO.BAR") + +(deftest :syntax/distinct/2 + (clsql:sql [distinct [foo :string] [bar :integer]]) + "DISTINCT FOO, BAR") + + +(deftest :syntax/order-by/1 + (clsql:sql [order-by [foo]]) + "ORDER BY FOO") + +(deftest :syntax/group-by/1 + (clsql:sql [group-by [foo]]) + "GROUP BY FOO") + +(deftest :syntax/group-by/2 + (clsql:sql + (clsql-sys::make-query [foo] [bar] [count [foo]] + :from [table] + :group-by '([foo] [bar]) + :order-by '([foo] [bar]))) + "SELECT FOO,BAR,COUNT(FOO) FROM TABLE GROUP BY FOO,BAR ORDER BY FOO,BAR") + + +(deftest :syntax/coalesce/1 + (clsql:sql [coalesce [foo] [bar] "not specified"]) + "COALESCE(FOO,BAR,'not specified')") + +(deftest :syntax/coalesce/2 + (clsql:sql [nvl [foo] "not specified"]) + "COALESCE(FOO,'not specified')") + +(deftest :syntax/nvl/1 + (clsql:sql [nvl [foo] "not specified"]) + "COALESCE(FOO,'not specified')") + + + +(deftest :syntax/sets/1 + (clsql:sql [union [select [foo] :from [bar]] [select [baz] :from [bar]]]) + "SELECT FOO FROM BAR UNION SELECT BAZ FROM BAR") + +(deftest :syntax/sets/2 + (clsql:sql [intersect [select [foo] :from [bar]] [select [baz] :from [bar]]]) + "SELECT FOO FROM BAR INTERSECT SELECT BAZ FROM BAR") + +(deftest :syntax/sets/3 + (clsql:sql [except [select [foo] :from [bar]] [select [baz] :from [bar]]]) + "SELECT FOO FROM BAR EXCEPT SELECT BAZ FROM BAR") + +(deftest :syntax/sets/4 + (clsql:sql [minus [select [foo] :from [bar]] [select [baz] :from [bar]]]) + "SELECT FOO FROM BAR MINUS SELECT BAZ FROM BAR") + + +(deftest :syntax/function/1 + (clsql:sql [function "COS" [age]]) + "COS(AGE)") + +(deftest :syntax/function/2 + (clsql:sql [function "TO_DATE" "02/06/99" "mm/DD/RR"]) + "TO_DATE('02/06/99','mm/DD/RR')") + + +(deftest :syntax/query/1 + (clsql:sql [select [person_id] [surname] :from [person]]) + "SELECT PERSON_ID,SURNAME FROM PERSON") + +(deftest :syntax/query/2 + (clsql:sql [select [foo] [bar *] + :from '([baz] [bar]) + :where [or [= [foo] 3] + [> [baz.quux] 10]]]) + "SELECT FOO,BAR.* FROM BAZ,BAR WHERE ((FOO = 3) OR (BAZ.QUUX > 10))") + +(deftest :syntax/query/3 + (clsql:sql [select [foo bar] [baz] + :from '([foo] [quux]) + :where [or [> [baz] 3] + [like [foo bar] "SU%"]]]) + "SELECT FOO.BAR,BAZ FROM FOO,QUUX WHERE ((BAZ > 3) OR (FOO.BAR LIKE 'SU%'))") + +(deftest :syntax/query/4 + (clsql:sql [select [count [*]] :from [emp]]) + "SELECT COUNT(*) FROM EMP") + + +(deftest :syntax/expression/1 + (clsql:sql + (clsql:sql-operation + 'select + (clsql:sql-expression :table 'foo :attribute 'bar) + (clsql:sql-expression :attribute 'baz) + :from (list + (clsql:sql-expression :table 'foo) + (clsql:sql-expression :table 'quux)) + :where + (clsql:sql-operation 'or + (clsql:sql-operation + '> + (clsql:sql-expression :attribute 'baz) + 3) + (clsql:sql-operation + 'like + (clsql:sql-expression :table 'foo + :attribute 'bar) + "SU%")))) + "SELECT FOO.BAR,BAZ FROM FOO,QUUX WHERE ((BAZ > 3) OR (FOO.BAR LIKE 'SU%'))") + +(deftest :syntax/expression/2 + (clsql:sql + (apply (clsql:sql-operator 'and) + (loop for table in '(thistime nexttime sometime never) + for count from 42 + collect + [function "BETWEEN" + (clsql:sql-expression :table table + :attribute 'bar) + (clsql:sql-operation '* [hip] [hop]) + count] + collect + [like (clsql:sql-expression :table table + :attribute 'baz) + (clsql:sql table)]))) + "(BETWEEN(THISTIME.BAR,(HIP * HOP),42) AND (THISTIME.BAZ LIKE 'THISTIME') AND BETWEEN(NEXTTIME.BAR,(HIP * HOP),43) AND (NEXTTIME.BAZ LIKE 'NEXTTIME') AND BETWEEN(SOMETIME.BAR,(HIP * HOP),44) AND (SOMETIME.BAZ LIKE 'SOMETIME') AND BETWEEN(NEVER.BAR,(HIP * HOP),45) AND (NEVER.BAZ LIKE 'NEVER'))" + ) + +(deftest :syntax/subqueries/query + (clsql:sql + (clsql:sql-operation 'select [*] + :from [foo] + :where [in [id] [select [id] :from [bar]]])) + "SELECT * FROM FOO WHERE (ID IN (SELECT ID FROM BAR))") + +(deftest :syntax/subqueries/delete + (clsql:sql + (make-instance 'clsql-sys::sql-delete + :from [foo] + :where [in [id] [select [id] :from [bar]]])) + "DELETE FROM FOO WHERE (ID IN (SELECT ID FROM BAR))") + +(deftest :syntax/subqueries/update + (clsql:sql + (make-instance 'clsql-sys::sql-update + :attributes (list [id]) + :values '(0) + :table [foo] + :where [in [id] [select [id] :from [bar]]])) + "UPDATE FOO SET ID = 0 WHERE (ID IN (SELECT ID FROM BAR))") + + )) + +(let ((tests '(((:foo) "FOO") + ((:foo-bar) "FOO_BAR") + (("foo") "\"foo\"") + (('|foo bar|) "\"foo bar\"") + ((:foo :table-alias :bar) "FOO BAR" ) + ((:foo_bar :table-alias :bar-bast) "FOO_BAR BAR_BAST") + (("foo" :table-alias "Bar") "\"foo\" \"Bar\"") + (('|foo bar| :table-alias :bast) "\"foo bar\" BAST")))) + + (push + `(deftest :syntax/sql-ident-table + (values ,@(mapcar + #'(lambda (args) + `(clsql:sql (make-instance 'clsql-sys:sql-ident-table + :name ,@args))) + (mapcar #'first tests))) + ,@(mapcar #'second tests)) + *rt-syntax*)) diff --git a/tests/test-time.lisp b/tests/test-time.lisp new file mode 100644 index 0000000..e5b4d64 --- /dev/null +++ b/tests/test-time.lisp @@ -0,0 +1,466 @@ +;;; -*- Mode: Lisp -*- +;;; +;;; Copyright (c) 2000, 2001 onShore Development, Inc. +;;; +;;; Test time functions (time.lisp) + +(in-package #:clsql-tests) +(clsql-sys:file-enable-sql-reader-syntax) + +(def-view-class datetest () + ((id :column "id" + :type integer + :db-kind :key + :db-constraints (:not-null :unique) + :accessor id :initarg :id + :initform nil + :db-type "int4") + (testtimetz :column "testtimetz" + :type clsql-sys:wall-time + :db-kind :base + :db-constraints nil + :accessor testtimetz :initarg :testtimetz + :initform nil + :db-type "timestamp with time zone") + (testtime :column "testtime" + :type clsql-sys:wall-time + :db-kind :base + :db-constraints nil + :accessor testtime :initarg :testtime + :initform nil + :db-type "timestamp without time zone"))) + +(def-dataset *ds-datetest* + (:setup (lambda () (clsql-sys:create-view-from-class 'datetest))) + (:cleanup "DROP TABLE datetest")) + + +(def-dataset *cross-platform-datetest* + (:setup (lambda () (create-table [datetest] + '(([testtime] wall-time))))) + (:cleanup (lambda () + (drop-table [datetest])))) + + +(setq *rt-time* + '( + +;; we use parse timestring a lot through here verifying other things +;; start off just checking that. +(deftest :time/iso-parse/0 + (let* ((time1 (parse-timestring "2010-01-23"))) + (decode-time time1)) + 0 0 0 0 23 1 2010 6) + +(deftest :time/iso-parse/1 + (let* ((time1 (parse-timestring "2010-01-23T14:56:32Z"))) + (decode-time time1)) + 0 32 56 14 23 1 2010 6) + +(deftest :time/iso-parse/2 + (let* ((time1 (parse-timestring "2008-02-29 12:46:32"))) + (decode-time time1)) + 0 32 46 12 29 2 2008 5) + +(deftest :time/iso-parse/3 + (let* ((time1 (parse-timestring "2010-01-23 14:56:32.44"))) + (decode-time time1)) + 440000 32 56 14 23 1 2010 6) + +(deftest :time/iso-parse/4 + (let* ((time1 (parse-timestring "2010-01-23 14:56:32.0044"))) + (decode-time time1)) + 4400 32 56 14 23 1 2010 6) + +(deftest :time/iso-parse/5 + (let* ((time1 (parse-timestring "2010-01-23 14:56:32.000003"))) + (decode-time time1)) + 3 32 56 14 23 1 2010 6) + +(deftest :time/print-parse/1 + ;;make sure when we print and parse we get the same time. + (let* ((time (clsql-sys:make-time :year 2010 :month 1 :day 4 + :hour 14 :minute 15 :second 44)) + (string-time (iso-timestring time)) + (time2 (parse-timestring string-time))) + (decode-time time2)) + 0 44 15 14 4 1 2010 1) + +(deftest :time/print-parse/2 + ;;make sure when we print and parse we get the same time. + (let* ((time (clsql-sys:make-time :year 2010 :month 1 :day 4 + :hour 14 :minute 15 :second 44 :usec 3)) + (string-time (iso-timestring time)) + (time2 (parse-timestring string-time))) + (decode-time time2)) + 3 44 15 14 4 1 2010 1) + + +;; relations of intervals +(deftest :time/1 + (let* ((time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-2 (clsql:parse-timestring "2002-01-01 11:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")) + (interval-1 (clsql:make-interval :start time-1 :end time-2)) + (interval-2 (clsql:make-interval :start time-2 :end time-3)) + (interval-3 (clsql:make-interval :start time-3 :end time-4)) + (interval-4 (clsql:make-interval :start time-1 :end time-3)) + (interval-5 (clsql:make-interval :start time-2 :end time-4)) + (interval-6 (clsql:make-interval :start time-1 :end time-4))) + (flet ((my-assert (number relation i1 i2) + (declare (ignore number)) + (let ((found-relation (clsql:interval-relation i1 i2))) + (equal relation found-relation)))) + (and + (my-assert 1 :contains interval-1 interval-1) + (my-assert 2 :precedes interval-1 interval-2) + (my-assert 3 :precedes interval-1 interval-3) + (my-assert 4 :contained interval-1 interval-4) + (my-assert 5 :precedes interval-1 interval-5) + (my-assert 6 :contained interval-1 interval-6) + (my-assert 7 :follows interval-2 interval-1) + (my-assert 8 :contains interval-2 interval-2) + (my-assert 9 :precedes interval-2 interval-3) + (my-assert 10 :contained interval-2 interval-4) + (my-assert 11 :contained interval-2 interval-5) + (my-assert 12 :contained interval-2 interval-6) + (my-assert 13 :follows interval-3 interval-1) + (my-assert 14 :follows interval-3 interval-2) + (my-assert 15 :contains interval-3 interval-3) + (my-assert 16 :follows interval-3 interval-4) + (my-assert 17 :contained interval-3 interval-5) + (my-assert 18 :contained interval-3 interval-6) + (my-assert 19 :contains interval-4 interval-1) + (my-assert 20 :contains interval-4 interval-2) + (my-assert 21 :precedes interval-4 interval-3) + (my-assert 22 :contains interval-4 interval-4) + (my-assert 23 :overlaps interval-4 interval-5) + (my-assert 24 :contained interval-4 interval-6) + (my-assert 25 :follows interval-5 interval-1) + (my-assert 26 :contains interval-5 interval-2) + (my-assert 27 :contains interval-5 interval-3) + (my-assert 28 :overlaps interval-5 interval-4) + (my-assert 29 :contains interval-5 interval-5) + (my-assert 30 :contained interval-5 interval-6) + (my-assert 31 :contains interval-6 interval-1) + (my-assert 32 :contains interval-6 interval-2) + (my-assert 33 :contains interval-6 interval-3) + (my-assert 34 :contains interval-6 interval-4) + (my-assert 35 :contains interval-6 interval-5) + (my-assert 36 :contains interval-6 interval-6)))) + t) + +;; adjacent intervals in list +(deftest :time/2 + (let* ((interval-list nil) + (time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))) + (setf interval-list + (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-3 + :type :open))) + (setf interval-list + (clsql:interval-push interval-list (clsql:make-interval :start time-3 :end time-4 + :type :open))) + (clsql:interval-relation (car interval-list) (cadr interval-list))) + :precedes) + +;; nested intervals in list +(deftest :time/3 + (let* ((interval-list nil) + (time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-2 (clsql:parse-timestring "2002-01-01 11:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))) + (setf interval-list + (clsql:interval-push interval-list (clsql:make-interval :start time-1 + :end time-4 + :type :open))) + (setf interval-list + (clsql:interval-push interval-list (clsql:make-interval :start time-2 + :end time-3 + :type :closed))) + (let* ((interval (car interval-list)) + (interval-contained + (when interval (car (clsql:interval-contained interval))))) + (when (and interval interval-contained) + (and (clsql:time= (clsql:interval-start interval) time-1) + (clsql:time= (clsql:interval-end interval) time-4) + (eq (clsql:interval-type interval) :open) + (clsql:time= (clsql:interval-start interval-contained) time-2) + (clsql:time= (clsql:interval-end interval-contained) time-3) + (eq (clsql:interval-type interval-contained) :closed))))) + t) + +;; interval-edit - nonoverlapping +(deftest :time/4 + (let* ((interval-list nil) + (time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-2 (clsql:parse-timestring "2002-01-01 11:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-2 :type :open))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-3 :end time-4 :type :closed))) + (setf interval-list (clsql:interval-edit interval-list time-1 time-1 time-3)) + ;; should be time-3 not time-2 + (clsql:time= (clsql:interval-end (car interval-list)) time-3)) + t) + +;; interval-edit - overlapping +(deftest :time/5 + (let* ((interval-list nil) + (time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-2 (clsql:parse-timestring "2002-01-01 11:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00"))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-2 :type :open))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-2 :end time-4 :type :closed))) + (let ((pass t)) + (handler-case + (progn + (setf interval-list + (clsql:interval-edit interval-list time-1 time-1 time-3)) + (setf pass nil)) + (error nil)) + pass)) + t) + +;; interval-edit - nested intervals in list +(deftest :time/6 + (let* ((interval-list nil) + (time-1 (clsql:parse-timestring "2002-01-01 10:00:00")) + (time-2 (clsql:parse-timestring "2002-01-01 11:00:00")) + (time-3 (clsql:parse-timestring "2002-01-01 12:00:00")) + (time-4 (clsql:parse-timestring "2002-01-01 13:00:00")) + (time-5 (clsql:parse-timestring "2002-01-01 14:00:00")) + (time-6 (clsql:parse-timestring "2002-01-01 15:00:00"))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-1 :end time-6 :type :open))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-2 :end time-3 :type :closed))) + (setf interval-list (clsql:interval-push interval-list (clsql:make-interval :start time-4 :end time-5 :type :closed))) + (setf interval-list (clsql:interval-edit interval-list time-1 time-1 time-4)) + ;; should be time-4 not time-6 + (clsql:time= (clsql:interval-end (car interval-list)) time-4)) + t) + +;; Test the boundaries of Local Time with granularity of 1 year +(deftest :time/7 + (let ((sec-in-year (* 60 60 24 365)) + (year (clsql:time-element (clsql:make-time) :year))) + (dotimes (n 50 n) + (let ((date (clsql:make-time :second (* n sec-in-year)))) + (unless (= (+ year n) + (clsql:time-element date :year)) + (return n))))) + 50) + +;; Test db-timestring +(deftest :time/9 + (flet ((grab-year (dbstring) + (parse-integer (subseq dbstring 1 5)))) + (let ((second-in-year (* 60 60 24 365))) + (dotimes (n 2000 n) + (let* ((second (* -1 n second-in-year)) + (date (clsql:make-time :year 2525 :second second))) + (unless + (= (grab-year (clsql:db-timestring date)) + (clsql:time-element date :year)) + (return n)))))) + 2000) + +;; Conversion between MJD and Gregorian +(deftest :time/10 + (dotimes (base 10000 base) + (unless (= (apply #'clsql:gregorian-to-mjd (clsql:mjd-to-gregorian base)) + base) + (return base))) + 10000) + +;; Clsql:Roll by minutes: +90 +(deftest :time/11 + (let ((now (clsql:get-time))) + (clsql:time= (clsql:time+ now (clsql:make-duration :minute 90)) + (clsql:roll now :minute 90))) + t) + +;;Clsql:Roll by minutes: +900 +(deftest :time/12 + (let ((now (clsql:get-time))) + (clsql:time= (clsql:time+ now (clsql:make-duration :minute 900)) + (clsql:roll now :minute 900))) + t) + + +;; Clsql:Roll by minutes: +900 +(deftest :time/13 + (let* ((now (clsql:get-time)) + (add-time (clsql:time+ now (clsql:make-duration :minute 9000))) + (roll-time (clsql:roll now :minute 9000))) + (clsql:time= add-time roll-time)) + t) + + + +;;; The cross platform dataset uses the 'timestamp' column type which is +;;; in sql-92, for all that means. + +(deftest :time/cross-platform/no-usec/no-tz + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "2008-09-09T14:37:29") :format :iso)) + +(deftest :time/cross-platform/no-usec/tz + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29-04:00"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "2008-09-09T14:37:29-04:00") :format :iso)) + +;;;This test gets at the databases that only support miliseconds, +;;; not microseconds. +(deftest :time/cross-platform/msec + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29.423"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "2008-09-09T14:37:29.423") :format :iso)) + +(deftest :time/cross-platform/usec/no-tz + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29.000213"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "2008-09-09T14:37:29.000213") :format :iso)) + +(deftest :time/cross-platform/usec/tz + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29.000213-04:00"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] + :limit 1 :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "2008-09-09T14:37:29.000213-04:00") :format :iso)) + + + + +;;; All odbc databases use local times exclusively (they do not send timezone info) +;;; Postgresql can use timezones, except when being used over odbc. This test when +;;; run through both postgres socket and postgres odbc should test a fairly +;;; broad swath of available problem space +;;; +;;; Things the following tests try to prove correct +;;; * Reading and writing usec and usec-less times +;;; * reading and writing timezones (Z=utc) when appropriate (eg: postgresql-socket) +;;; * reading and writing localtimes when appropriate (eg: ODBC) +;;; * reading and writing through both the oodml and fdml layers + + + +(deftest :time/pg/fdml/usec + (with-dataset *ds-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29.000213-04:00"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtimetz] [testtime] [id]) + :values (list time time 1)) + (destructuring-bind (testtimetz testtime) + (first (clsql:select [testtimetz] [testtime] + :from [datetest] + :limit 1 :flatp t + :where [= [testtime] time] )) + (values (iso-timestring (parse-timestring testtime)) + (iso-timestring (parse-timestring testtimetz)))))) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000213-04:00")) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000213-04:00"))) + +(deftest :time/pg/oodml/no-usec + (with-dataset *ds-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29-04:00"))) + (clsql-sys:update-records-from-instance + (make-instance 'datetest :testtimetz time :testtime time :id 1)) + (let ((o (first (clsql:select + 'datetest + :limit 1 :flatp t + :where [= [testtime] time] )))) + (assert o (o) "o shouldnt be null here (we should have just inserted)") + (update-records-from-instance o) + (update-instance-from-records o) + (values (iso-timestring (testtime o)) + (iso-timestring (testtimetz o)))))) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29-04:00")) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29-04:00"))) + +(deftest :time/pg/oodml/usec + (with-dataset *ds-datetest* + (let ((time (parse-timestring "2008-09-09T14:37:29.000278-04:00"))) + (clsql-sys:update-records-from-instance + (make-instance 'datetest :testtimetz time :testtime time :id 1)) + (let ((o (first (clsql:select + 'datetest + :limit 1 :flatp t + :where [= [testtime] time] )))) + (assert o (o) "o shouldnt be null here (we should have just inserted)") + (update-records-from-instance o) + (update-instance-from-records o) + (values (iso-timestring (testtime o)) + (iso-timestring (testtimetz o))) + ))) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000278-04:00")) + #.(iso-timestring (parse-timestring "2008-09-09T14:37:29.000278-04:00"))) + +(deftest :time/historic-datetimes + (with-dataset *cross-platform-datetest* + (let ((time (parse-timestring "1800-09-09T14:37:29"))) + (clsql-sys:insert-records :into [datetest] + :attributes '([testtime]) + :values (list time)) + (let ((testtime + (first (clsql:select [testtime] + :from [datetest] :flatp t + :where [= [testtime] time] )))) + (format-time nil (parse-timestring testtime) :format :iso) + ))) + #.(format-time nil (parse-timestring "1800-09-09T14:37:29") :format :iso)) + +)) + + + + + diff --git a/tests/utils.lisp b/tests/utils.lisp new file mode 100644 index 0000000..396e303 --- /dev/null +++ b/tests/utils.lisp @@ -0,0 +1,100 @@ +;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: utils.lisp +;;;; Purpose: Classes and utilities for testing +;;;; Author: Kevin M. Rosenberg +;;;; Created: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-tests) + +(defun %get-int (v) + (etypecase v + (string (parse-integer v :junk-allowed t)) + (integer v) + (number (truncate v)))) + +(defvar *config-pathname* + (make-pathname :defaults (user-homedir-pathname) + :name ".clsql-test" + :type "config")) + +(defvar +all-db-types+ + '(:postgresql :postgresql-socket :postgresql-socket3 :mysql :sqlite :sqlite3 :odbc :oracle + #+allegro :aodbc)) + +(defclass conn-specs () + ((aodbc :accessor aodbc-spec :initform nil) + (mysql :accessor mysql-spec :initform nil) + (postgresql :accessor postgresql-spec :initform nil) + (postgresql-socket :accessor postgresql-socket-spec :initform nil) + (postgresql-socket3 :accessor postgresql-socket3-spec :initform nil) + (sqlite :accessor sqlite-spec :initform nil) + (sqlite3 :accessor sqlite3-spec :initform nil) + (odbc :accessor odbc-spec :initform nil) + (oracle :accessor oracle-spec :initform nil)) + (:documentation "Connection specs for CLSQL testing")) + + +(defun read-specs (&optional (path *config-pathname*)) + (if (probe-file path) + (with-open-file (stream path :direction :input) + (let ((specs (make-instance 'conn-specs))) + (dolist (spec (read stream) specs) + (push (second spec) + (slot-value specs (intern (symbol-name (first spec)) + (find-package '#:clsql-tests))))))) + (progn + (warn "CLSQL test config file ~S not found" path) + nil))) + +(defun spec-fn (db-type) + (intern (concatenate 'string (symbol-name db-type) + (symbol-name '#:-spec)) + (find-package '#:clsql-tests))) + +(defun db-type-spec (db-type specs) + (funcall (spec-fn db-type) specs)) + + +(defun summarize-test-report (sexp &optional (output *standard-output*)) + (flet ((db-title (db-type underlying-db-type) + (format nil "~A~A" + db-type + (if (eq db-type underlying-db-type) + "" + (format nil "/~A" underlying-db-type))))) + (with-open-file (in sexp :direction :input) + (let ((eof (cons nil nil))) + (do ((form (read in nil eof) (read in nil eof))) + ((eq form eof)) + (destructuring-bind (db-type + underlying-db-type + utime + total-tests + failed-tests + impl-type + impl-version + machine-type) + form + (declare (ignorable utime impl-version)) + (if failed-tests + (format output "~&~A: ~D of ~D tests failed (~A, ~A).~&" + (db-title db-type underlying-db-type) + (length failed-tests) + total-tests + machine-type + impl-type) + (format output "~&~A: All ~D tests passed (~A, ~A).~%" + (db-title db-type underlying-db-type) + total-tests + machine-type + impl-type)))))))) diff --git a/uffi/Makefile b/uffi/Makefile new file mode 100644 index 0000000..8ae1fd9 --- /dev/null +++ b/uffi/Makefile @@ -0,0 +1,77 @@ +#!/usr/bin/make +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL UFFI interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2006 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. + +SUBDIRS= + +include ../Makefile.common + +base=clsql_uffi +source=$(base).c +object=$(base).o +shared_lib=$(base).so +shared64_lib=$(base)64.so +dylib=$(base).dylib + +ifneq ($(OS_DPKG_BUILDFLAGS),0) + CFLAGS:=$(CFLAGS) $(shell dpkg-buildflags --get CFLAGS) + LDFLAGS:=$(LDFLAGS) $(shell DEB_BUILD_MAINT_OPTIONS=hardening=+all,-pie dpkg-buildflags --get LDFLAGS | sed "s/-Wl\|,/ /ig") +endif + + +.PHONY: all +all: $(shared_lib) + +$(shared_lib): $(source) Makefile +ifneq ($(OS_AIX),0) + gcc -c -D_BSD -D_NO_PROTO -D_NONSTD_TYPES -D_MBI=void $(source) + make_shared -o $(shared_lib) $(object) +else + ifneq ($(OS_SUNOS),0) + cc -KPIC -c $(source) -o $(object) + cc -G $(object) -o $(shared_lib) + else + ifneq ($(OS_DARWIN64),0) + cc -arch x86_64 -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress $(source) -o $(dylib) + cc -arch x86_64 -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz.dylib -o z.dylib + else + ifneq ($(OS_DARWIN),0) + cc -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress $(source) -o $(dylib) + cc -arch i386 -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz.dylib -o z.dylib + else + ifneq ($(OS_CYGWIN),0) + gcc -c $(source) -o $(object) + ld -shared -soname=$(base) $(LDFLAGS) $(object) -o $(shared_lib) + else + ifneq ($(OS_LINUX64),0) + gcc $(CFLAGS) -fPIC -DPIC -c $(source) -o $(object) + gcc $(LDFLAGS) -fPIC -shared -Wl,-soname=$(base) -lc $(object) -o $(shared64_lib) + rm -f $(object) + #@echo "Ensure that you have multiarch i386 build tools if you want to build 32-bit library" + #-gcc -m32 $(CFLAGS) -fPIC -DPIC -c $(source) -o $(object) + #-gcc -m32 $(LDFLAGS) -fPIC -shared -Wl,-soname=$(base) -lc $(object) -o $(shared_lib) + else + gcc -fPIC -DPIC -c $(source) -o $(object) + ld -shared -soname=$(base) -lc $(object) -o $(shared_lib) + endif + endif + endif + endif +endif +endif + rm -f $(object) + + +.PHONY: distclean +distclean: clean + @rm -f $(dylib) $(shared_lib) $(shared64_lib) $(object) z.dylib diff --git a/uffi/Makefile.32+64bits b/uffi/Makefile.32+64bits new file mode 100644 index 0000000..68d9743 --- /dev/null +++ b/uffi/Makefile.32+64bits @@ -0,0 +1,100 @@ +#!/usr/bin/make +########################################################################## +# FILE IDENTIFICATION +# +# Name: Makefile +# Purpose: Makefile for CLSQL UFFI interface +# Programer: Kevin M. Rosenberg +# Date Started: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################## + + +SUBDIRS= + +include ../Makefile.common + +PTRBITS:=$(shell gcc -o ptrbits ptrbits.c && ./ptrbits) + +base:=clsql_uffi +basebits:=$(base) +ifeq ($(PTRBITS),64) + basebits:=$(base)64 +endif + +source=$(base).c +object=$(basebits).o +shared_lib=$(basebits).so +shared_lib32=$(base)32.so +object32=$(base)32.o + +.PHONY: all +ifeq ($(PTRBITS),64) + all: $(shared_lib) $(shared_lib32) +else + all: $(shared_lib) +endif + +$(shared_lib): $(source) Makefile +ifneq ($(OS_AIX),0) + gcc -c -D_BSD -D_NO_PROTO -D_NONSTD_TYPES -D_MBI=void $(source) + make_shared -o $(shared_lib) $(object) +else + ifneq ($(OS_SUNOS),0) + cc -KPIC -c $(source) -o $(object) + cc -G $(object) -o $(shared_lib) + else + ifneq ($(OS_DARWIN),0) + cc -dynamic -c $(source) -o $(object) + ld -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress -o $(base).dylib $(object) + ld -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz.dylib -o z.dylib + else + ifneq ($(OS_CYGWIN),0) + gcc -c $(source) -o $(object) + ld -shared -soname=$(base) $(LDFLAGS) $(object) -o $(shared_lib) + else + gcc -fPIC -DPIC -c $(source) -o $(object) + ld -shared -soname=$(base) -lc $(object) -o $(shared_lib) + endif + endif + endif +endif + rm $(object) + +ifeq ($(PTRBITS),64) +$(shared_lib32): $(source) Makefile + ifneq ($(OS_AIX),0) + gcc -m32 -c -D_BSD -D_NO_PROTO -D_NONSTD_TYPES -D_MBI=void $(source) -o ($object32) + make_shared -o $(shared_lib32) $(object32) + else + ifneq ($(OS_SUNOS),0) + cc -m32 -KPIC -c $(source) -o $(object32) + cc -G $(object32) -o $(shared_lib32) + else + ifneq ($(OS_DARWIN),0) + cc -m32 -dynamic -c $(source) -o $(object32) + ld -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress -o $(base)32.dylib $(object32) + ld -bundle /usr/lib/bundle1.o -flat_namespace -undefined suppress /usr/lib/libz32.dylib -o z.dylib + else + ifneq ($(OS_CYGWIN),0) + gcc -m32 -c $(source) -o $(object32) + ld -shared -soname=$(base) $(LDFLAGS) $(object32) -o $(shared_lib32) + else + gcc -m32 -fPIC -DPIC -c $(source) -o $(object32) + ld -shared -soname=$(base) -L /lib32 -L /usr/lib32 -lc $(object32) -o $(shared_lib32) + endif + endif + endif + endif +endif + rm $(object32) + +.PHONY: distclean +distclean: clean + rm -f $(base).dylib $(base).dylib $(basebits).so $(base).o ptrbits + diff --git a/uffi/Makefile.msvc b/uffi/Makefile.msvc new file mode 100644 index 0000000..ea3b5b7 --- /dev/null +++ b/uffi/Makefile.msvc @@ -0,0 +1,31 @@ +# -*- Mode: Makefile -*- +########################################################################### +# FILE IDENTIFICATION +# +# Name: Makefile.msvc +# Purpose: Makefile for the CLSQL UFFI helper package (MSVC) +# Author: Kevin M. Rosenberg +# Created: Mar 2002 +# +# This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +# +# CLSQL users are granted the rights to distribute and use this software +# as governed by the terms of the Lisp Lesser GNU Public License +# (http://opensource.franz.com/preamble.html), also known as the LLGPL. +########################################################################### + + +BASE=clsql_uffi + +# Nothing to configure beyond here + +SRC=$(BASE).c +OBJ=$(BASE).obj +DLL=$(BASE).dll + +$(DLL): $(SRC) + cl /MD /LD -D_MT /DWIN32=1 /D__LCC__=1 $(SRC) + del $(OBJ) $(BASE).exp + +clean: + del /q $(DLL) $(BASE).LIB diff --git a/uffi/clsql-uffi-loader.lisp b/uffi/clsql-uffi-loader.lisp new file mode 100644 index 0000000..8b12cc8 --- /dev/null +++ b/uffi/clsql-uffi-loader.lisp @@ -0,0 +1,52 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-uffi-loader.sql +;;;; Purpose: Library loader using CLSQL UFFI helper library +;;;; Author: Kevin M. Rosenberg +;;;; Created: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-uffi) + +(defun find-and-load-foreign-library (filenames &key module supporting-libraries (errorp t)) + "Attempt to load a foreign library. This will search for any of the filenames, as +well as any of the filenames in any of the clsql:*foreign-library-search-paths*" + (setq filenames (if (listp filenames) filenames (list filenames))) + + (flet ((try-load (testpath) + (handler-case + (uffi:load-foreign-library testpath + :module module + :supporting-libraries supporting-libraries) + (error nil)))) ;(c) (warn "~A" c) nil)))) + (or + (loop for type in (uffi:foreign-library-types) + thereis + (loop for name in filenames + for pn = (make-pathname :name name :type type) + thereis (or + (try-load pn) + (loop for search-path in clsql:*foreign-library-search-paths* + thereis (try-load (merge-pathnames pn search-path)))))) + (when errorp + (error "Couldn't load foreign librar~@P ~{~S~^, ~}. (searched ~S: ~S)" + (length filenames) filenames + 'clsql:*foreign-library-search-paths* clsql:*foreign-library-search-paths*))))) + +;; searches clsql_uffi64 to accomodate both 32-bit and 64-bit libraries on same system +(defvar *clsql-uffi-library-filenames* + `(,@(when (> most-positive-fixnum (expt 2 32)) (list "clsql_uffi64")) + "clsql_uffi")) + +(defvar *clsql-uffi-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld to +load the MySQL client library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") diff --git a/uffi/clsql-uffi-package.lisp b/uffi/clsql-uffi-package.lisp new file mode 100644 index 0000000..2b792f4 --- /dev/null +++ b/uffi/clsql-uffi-package.lisp @@ -0,0 +1,32 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-uffi-package.cl +;;;; Purpose: Package definitions for common UFFI interface routines +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:cl-user) + +(defpackage #:clsql-uffi + (:use #:cl #:uffi) + (:export + #:find-and-load-foreign-library + #:canonicalize-type-list + #:convert-raw-field + #:atoi + #:atol + #:atof + #:make-64-bit-integer + #:make-128-bit-integer + #:split-64-bit-integer) + (:documentation "Common functions for interfaces using UFFI")) + diff --git a/uffi/clsql-uffi.lisp b/uffi/clsql-uffi.lisp new file mode 100644 index 0000000..6f62d95 --- /dev/null +++ b/uffi/clsql-uffi.lisp @@ -0,0 +1,167 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-uffi.cl +;;;; Purpose: Common functions for interfaces using UFFI +;;;; Programmers: Kevin M. Rosenberg +;;;; Date Started: Mar 2002 +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg +;;;; +;;;; CLSQL users are granted the rights to distribute and use this software +;;;; as governed by the terms of the Lisp Lesser GNU Public License +;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. +;;;; ************************************************************************* + +(in-package #:clsql-uffi) + + +(defun canonicalize-type-list (types auto-list) + "Ensure a field type list meets expectations" + (declare (optimize (speed 3) (safety 0))) + (do ((i 0 (1+ i)) + (new-types '()) + (length-types (length types)) + (length-auto-list (length auto-list))) + ((= i length-auto-list) + (nreverse new-types)) + (declare (fixnum length-types length-auto-list i)) + (if (>= i length-types) + (push t new-types) ;; types is shorter than num-fields + (push + (case (nth i types) + (:int + (case (nth i auto-list) + (:int32 + :int32) + (:int64 + :int64) + (t + t))) + (:double + (case (nth i auto-list) + (:double + :double) + (t + t))) + (:int32 + (if (eq :int32 (nth i auto-list)) + :int32 + t)) + (:int64 + (if (eq :int64 (nth i auto-list)) + :int64 + t)) + (:blob + :blob) + (:uint + :uint) + (t + t)) + new-types)))) + +(uffi:def-function "atoi" + ((str (* :unsigned-char))) + :returning :int) + +(uffi:def-function ("strtoul" c-strtoul) + ((str (* :unsigned-char)) + (endptr (* :unsigned-char)) + (radix :int)) + :returning :unsigned-long) + +#-windows +(uffi:def-function ("strtoull" c-strtoull) + ((str (* :unsigned-char)) + (endptr (* :unsigned-char)) + (radix :int)) + :returning :unsigned-long-long) + +#-windows +(uffi:def-function ("strtoll" c-strtoll) + ((str (* :unsigned-char)) + (endptr (* :unsigned-char)) + (radix :int)) + :returning :long-long) + +#+windows +(uffi:def-function ("_strtoui64" c-strtoull) + ((str (* :unsigned-char)) + (endptr (* :unsigned-char)) + (radix :int)) + :returning :unsigned-long-long) + +#+windows +(uffi:def-function ("_strtoi64" c-strtoll) + ((str (* :unsigned-char)) + (endptr (* :unsigned-char)) + (radix :int)) + :returning :long-long) + +(uffi:def-function "atol" + ((str (* :unsigned-char))) + :returning :long) + +(uffi:def-function "atof" + ((str (* :unsigned-char))) + :returning :double) + +(uffi:def-constant +2^32+ 4294967296) +(uffi:def-constant +2^64+ 18446744073709551616) +(uffi:def-constant +2^32-1+ (1- +2^32+)) + +(defmacro make-64-bit-integer (high32 low32) + `(if (zerop (ldb (byte 1 31) ,high32)) + (+ ,low32 (ash ,high32 32)) + (- (+ ,low32 (ash ,high32 32)) +2^64+))) + +;; From high to low ints +(defmacro make-128-bit-integer (a b c d) + `(+ ,d (ash ,c 32) (ash ,b 64) (ash ,a 96))) + +(defmacro split-64-bit-integer (int64) + `(values (ash ,int64 -32) (logand ,int64 +2^32-1+))) + +(uffi:def-type char-ptr-def (* :unsigned-char)) + +(defun strtoul (char-ptr) + (declare (optimize (speed 3) (safety 0) (space 0)) + (type char-ptr-def char-ptr)) + (c-strtoul char-ptr uffi:+null-cstring-pointer+ 10)) + +(defun strtoull (char-ptr) + (declare (optimize (speed 3) (safety 0) (space 0)) + (type char-ptr-def char-ptr)) + (c-strtoull char-ptr uffi:+null-cstring-pointer+ 10)) + +(defun strtoll (char-ptr) + (declare (optimize (speed 3) (safety 0) (space 0)) + (type char-ptr-def char-ptr)) + (c-strtoll char-ptr uffi:+null-cstring-pointer+ 10)) + +(defun convert-raw-field (char-ptr type &key length encoding) + (declare (optimize (speed 3) (safety 0) (space 0)) + (type char-ptr-def char-ptr)) + (unless (uffi:null-pointer-p char-ptr) + (case type + (:double (atof char-ptr)) + (:int (atol char-ptr)) + (:int32 (atoi char-ptr)) + (:uint32 (strtoul char-ptr)) + (:uint (strtoul char-ptr)) + (:int64 (strtoll char-ptr)) + (:uint64 (strtoull char-ptr)) + (:blob + (if length + (uffi:convert-from-foreign-usb8 char-ptr length) + (error "Can't return blob since length is not specified."))) + (t + ;; NB: this used to manually expand the arg list based on if length and encoding + ;; were provided. If this is required the macro is aweful and should be rewritten + ;; to accept nil args (as it appears to) + (uffi:convert-from-foreign-string + char-ptr + :null-terminated-p (null length) + :length length + :encoding encoding))))) diff --git a/uffi/clsql_uffi.c b/uffi/clsql_uffi.c new file mode 100644 index 0000000..95822b7 --- /dev/null +++ b/uffi/clsql_uffi.c @@ -0,0 +1,73 @@ +/**************************************************************************** + * FILE IDENTIFICATION + * + * Name: clsql-uffi.c + * Purpose: Helper functions for common interfaces using UFFI + * Programmer: Kevin M. Rosenberg + * Date Started: Mar 2002 + * + * This file, part of CLSQL, is Copyright (c) 2002-2010 by Kevin M. Rosenberg + * + * CLSQL users are granted the rights to distribute and use this software + * as governed by the terms of the Lisp Lesser GNU Public License + * (http://opensource.franz.com/preamble.html), also known as the LLGPL. + ***************************************************************************/ + +#if defined(WIN32)||defined(WIN64) +#include + +BOOL WINAPI DllEntryPoint(HINSTANCE hinstdll, DWORD fdwReason, + LPVOID lpvReserved) +{ + return 1; +} + +#define DLLEXPORT __declspec(dllexport) + +#else +#define DLLEXPORT +#endif + + +const unsigned int bitmask_32bits = 0xFFFFFFFF; +#define lower_32bits(int64) ((unsigned int) int64 & bitmask_32bits) +#define upper_32bits(int64) ((unsigned int) (int64 >> 32)) + +/* Reads a 64-bit integer string, returns result as two 32-bit integers */ + +DLLEXPORT +unsigned int +atol64 (const unsigned char* str, unsigned int* pHigh32) +{ +#if defined(WIN32)||defined(WIN64) + __int64 result = 0; +#else + long long result = 0; +#endif + int minus = 0; + int first_char = *str; + if (first_char == '+') + ++str; + else if (first_char == '-') { + minus = 1; + ++str; + } + + while (*str) { + int i = *str - '0'; + if (i < 0 || i > 9) /* Non-numeric character -- quit */ + break; + result = i + (10 * result); + str++; + } + if (minus) + result = -result; + + *pHigh32 = upper_32bits(result); + return lower_32bits(result); +} + + + + + diff --git a/uffi/clsql_uffi.lib b/uffi/clsql_uffi.lib new file mode 100644 index 0000000..a9ee8fe Binary files /dev/null and b/uffi/clsql_uffi.lib differ diff --git a/uffi/ptrbits.c b/uffi/ptrbits.c new file mode 100644 index 0000000..17f7bf8 --- /dev/null +++ b/uffi/ptrbits.c @@ -0,0 +1,11 @@ +/* Prints the number of bits in a pointer. + * Copyright (c) 2006 Kevin Rosenberg + */ + +#include + +int main() { + char *p; + printf ("%d\n", 8*sizeof(p)); + return (0); +} -- cgit v1.2.3