r8821: integrate usql support
[clsql.git] / usql / table.lisp
diff --git a/usql/table.lisp b/usql/table.lisp
new file mode 100644 (file)
index 0000000..715cef0
--- /dev/null
@@ -0,0 +1,320 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; ======================================================================
+;;;; File:    table.lisp
+;;;; Updated: <04/04/2004 12:05:03 marcusp>
+;;;; ======================================================================
+;;;;
+;;;; Description ==========================================================
+;;;; ======================================================================
+;;;;
+;;;; The CLSQL-USQL Functional Data Definition Language (FDDL)
+;;;; including functions for schema manipulation. Currently supported
+;;;; SQL objects include tables, views, indexes, attributes and
+;;;; sequences.
+;;;;
+;;;; ======================================================================
+
+(in-package :clsql-usql-sys)
+
+
+;; Utilities
+
+(defun database-identifier (name)
+  (sql-escape (etypecase name
+                (string
+                 (string-upcase name))
+                (sql-ident
+                 (sql-output name))
+                (symbol
+                 (sql-output name)))))
+
+
+;; Tables 
+
+(defvar *table-schemas* (make-hash-table :test #'equal)
+  "Hash of schema name to table lists.")
+
+(defun create-table (name description &key (database *default-database*)
+                          (constraints nil))
+  "Create a table called NAME, in DATABASE which defaults to
+*DEFAULT-DATABASE*, containing the attributes in DESCRIPTION which is
+a list containing lists of attribute-name and type information pairs."
+  (let* ((table-name (etypecase name 
+                       (symbol (sql-expression :attribute name))
+                       (string (sql-expression :attribute (make-symbol name)))
+                       (sql-ident name)))
+         (stmt (make-instance 'sql-create-table
+                              :name table-name
+                              :columns description
+                              :modifiers constraints)))
+    (pushnew table-name (gethash *default-schema* *table-schemas*)
+             :test #'equal)
+    (execute-command stmt :database database)))
+
+(defun drop-table (name &key (if-does-not-exist :error)
+                        (database *default-database*))
+  "Drops table 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."
+  (let ((table-name (database-identifier name)))
+    (ecase if-does-not-exist
+      (:ignore
+       (unless (table-exists-p table-name :database database)
+         (return-from drop-table nil)))
+      (:error
+       t))
+    (let ((expr (concatenate 'string "DROP TABLE " table-name)))
+      (execute-command expr :database database))))
+
+(defun list-tables (&key (owner nil) (database *default-database*))
+  "List all tables in DATABASE which defaults to
+*DEFAULT-DATABASE*. If OWNER is nil, only user-owned tables are
+considered. This is the default. If OWNER is :all , all tables are
+considered. If OWNER is a string, this denotes a username and only
+tables owned by OWNER are considered. Table names are returned as a
+list of strings."
+  (database-list-tables database :owner owner))
+
+(defun table-exists-p (name &key (owner nil) (database *default-database*))
+  "Test for existence of an SQL table called NAME in DATABASE which
+defaults to *DEFAULT-DATABASE*. If OWNER is nil, only user-owned
+tables are considered. This is the default. If OWNER is :all , all
+tables are considered. If OWNER is a string, this denotes a username
+and only tables owned by OWNER are considered. Table names are
+returned as a list of strings."
+  (when (member (database-identifier name)
+                (list-tables :owner owner :database database)
+                :test #'string-equal)
+    t))
+
+
+;; Views 
+
+(defvar *view-schemas* (make-hash-table :test #'equal)
+  "Hash of schema name to view lists.")
+
+(defun create-view (name &key as column-list (with-check-option nil)
+                         (database *default-database*))
+  "Creates a view called NAME using the AS query and the optional
+COLUMN-LIST and WITH-CHECK-OPTION. The COLUMN-LIST argument is a list
+of columns to add to the view. The WITH-CHECK-OPTION adds 'WITH CHECK
+OPTION' to the resulting SQL. The default value of WITH-CHECK-OPTION
+is NIL. The default value of DATABASE is *DEFAULT-DATABASE*."
+  (let* ((view-name (etypecase name 
+                      (symbol (sql-expression :attribute name))
+                      (string (sql-expression :attribute (make-symbol name)))
+                      (sql-ident name)))
+         (stmt (make-instance 'sql-create-view
+                              :name view-name
+                              :column-list column-list
+                              :query as
+                              :with-check-option with-check-option)))
+    (pushnew view-name (gethash *default-schema* *view-schemas*) :test #'equal)
+    (execute-command stmt :database database)))
+
+(defun drop-view (name &key (if-does-not-exist :error)
+                       (database *default-database*))
+  "Deletes view 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."
+  (let ((view-name (database-identifier name)))
+    (ecase if-does-not-exist
+      (:ignore
+       (unless (view-exists-p view-name :database database)
+         (return-from drop-view)))
+      (:error
+       t))
+    (let ((expr (concatenate 'string "DROP VIEW " view-name)))
+      (execute-command expr :database database))))
+
+(defun list-views (&key (owner nil) (database *default-database*))
+  "List all views in DATABASE which defaults to *DEFAULT-DATABASE*. If
+OWNER is nil, only user-owned views are considered. This is the
+default. If OWNER is :all , all views are considered. If OWNER is a
+string, this denotes a username and only views owned by OWNER are
+considered. View names are returned as a list of strings."
+  (database-list-views database :owner owner))
+
+(defun view-exists-p (name &key (owner nil) (database *default-database*))
+  "Test for existence of an SQL view called NAME in DATABASE which
+defaults to *DEFAULT-DATABASE*. If OWNER is nil, only user-owned views
+are considered. This is the default. If OWNER is :all , all views are
+considered. If OWNER is a string, this denotes a username and only
+views owned by OWNER are considered. View names are returned as a list
+of strings."
+  (when (member (database-identifier name)
+                (list-views :owner owner :database database)
+                :test #'string-equal)
+    t))
+
+
+;; Indexes 
+
+(defvar *index-schemas* (make-hash-table :test #'equal)
+  "Hash of schema name to index lists.")
+
+(defun create-index (name &key on (unique nil) attributes
+                          (database *default-database*))
+  "Creates an index called NAME on the table specified by ON. The
+attributes of the table to index are given by ATTRIBUTES. Setting
+UNIQUE to T includes UNIQUE in the SQL index command, specifying that
+the columns indexed must contain unique values. The default value of
+UNIQUE is nil. The default value of DATABASE is *DEFAULT-DATABASE*."
+  (let* ((index-name (database-identifier name))
+         (table-name (database-identifier on))
+         (attributes (mapcar #'database-identifier (listify attributes)))
+         (stmt (format nil "CREATE ~A INDEX ~A ON ~A (~{~A~^, ~})"
+                       (if unique "UNIQUE" "")
+                       index-name table-name attributes)))
+    (pushnew index-name (gethash *default-schema* *index-schemas*))
+    (execute-command stmt :database database)))
+
+(defun drop-index (name &key (if-does-not-exist :error)
+                        (on nil)
+                        (database *default-database*))
+  "Deletes index NAME from table FROM 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."
+  (let ((index-name (database-identifier name)))
+    (ecase if-does-not-exist
+      (:ignore
+       (unless (index-exists-p index-name :database database)
+         (return-from drop-index)))
+      (:error t))
+    (execute-command (format nil "DROP INDEX ~A~A" index-name
+                             (if (null on) ""
+                                 (concatenate 'string " ON "
+                                              (database-identifier on))))
+                     :database database)))
+
+(defun list-indexes (&key (owner nil) (database *default-database*))
+  "List all indexes in DATABASE, which defaults to
+*default-database*. If OWNER is :all , all indexs are considered. If
+OWNER is a string, this denotes a username and only indexs owned by
+OWNER are considered. Index names are returned as a list of strings."
+  (database-list-indexes database :owner owner))
+  
+(defun index-exists-p (name &key (owner nil) (database *default-database*))
+  "Test for existence of an index called NAME in DATABASE which
+defaults to *DEFAULT-DATABASE*. If OWNER is :all , all indexs are
+considered. If OWNER is a string, this denotes a username and only
+indexs owned by OWNER are considered. Index names are returned as a
+list of strings."
+  (when (member (database-identifier name)
+                (list-indexes :owner owner :database database)
+                :test #'string-equal)
+    t))
+
+;; Attributes 
+
+(defun list-attributes (name &key (owner nil) (database *default-database*))
+  "List the attributes of a attribute called NAME in DATABASE which
+defaults to *DEFAULT-DATABASE*. If OWNER is nil, only user-owned
+attributes are considered. This is the default. If OWNER is :all , all
+attributes are considered. If OWNER is a string, this denotes a
+username and only attributes owned by OWNER are considered. Attribute
+names are returned as a list of strings. Attributes are returned as a
+list of strings."
+  (database-list-attributes (database-identifier name) database :owner owner))
+
+(defun attribute-type (attribute table &key (owner nil)
+                                 (database *default-database*))
+  "Return the field type of the ATTRIBUTE in TABLE.  The optional
+keyword argument DATABASE specifies the database to query, defaulting
+to *DEFAULT-DATABASE*. If OWNER is nil, only user-owned attributes are
+considered. This is the default. If OWNER is :all , all attributes are
+considered. If OWNER is a string, this denotes a username and only
+attributes owned by OWNER are considered. Attribute names are returned
+as a list of strings. Attributes are returned as a list of strings."
+  (database-attribute-type (database-identifier attribute)
+                           (database-identifier table)
+                           database
+                           :owner owner))
+
+(defun list-attribute-types (table &key (owner nil)
+                                   (database *default-database*))
+  "Returns type information for the attributes in TABLE from DATABASE
+which has a default value of *default-database*. If OWNER is nil, only
+user-owned attributes are considered. This is the default. If OWNER is
+:all, all attributes are considered. If OWNER is a string, this
+denotes a username and only attributes owned by OWNER are
+considered. Returns a list in which each element is a list (attribute
+datatype). Attribute is a string denoting the atribute name. Datatype
+is the vendor-specific type returned by ATTRIBUTE-TYPE."
+  (mapcar #'(lambda (type)
+              (list type (attribute-type type table :database database
+                                         :owner owner)))
+          (list-attributes table :database database :owner owner)))
+
+;(defun add-attribute (table attribute &key (database *default-database*))
+;  (database-add-attribute table attribute database))
+
+;(defun rename-attribute (table oldatt newname
+;                               &key (database *default-database*))
+;  (error "(rename-attribute ~a ~a ~a ~a) is not implemented"
+;         table oldatt newname database))
+
+
+;; Sequences 
+
+(defvar *sequence-schemas* (make-hash-table :test #'equal)
+  "Hash of schema name to sequence lists.")
+
+(defun create-sequence (name &key (database *default-database*))
+  "Create a sequence called NAME in DATABASE which defaults to
+*DEFAULT-DATABASE*."
+  (let ((sequence-name (database-identifier name)))
+    (database-create-sequence sequence-name database)
+    (pushnew sequence-name (gethash *default-schema* *sequence-schemas*)
+             :test #'equal))
+  (values))
+
+(defun drop-sequence (name &key (if-does-not-exist :error)
+                           (database *default-database*))
+  "Drops sequence 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."
+  (let ((sequence-name (database-identifier name)))
+    (ecase if-does-not-exist
+      (:ignore
+       (unless (sequence-exists-p sequence-name :database database)
+         (return-from drop-sequence)))
+      (:error t))
+    (database-drop-sequence sequence-name database))
+  (values))
+
+(defun list-sequences (&key (owner nil) (database *default-database*))
+  "List all sequences in DATABASE, which defaults to
+*default-database*. If OWNER is nil, only user-owned sequences are
+considered. This is the default. If OWNER is :all , all sequences are
+considered. If OWNER is a string, this denotes a username and only
+sequences owned by OWNER are considered. Sequence names are returned
+as a list of strings."
+  (database-list-sequences database :owner owner))
+
+(defun sequence-exists-p (name &key (owner nil)
+                               (database *default-database*))
+  "Test for existence of a sequence called NAME in DATABASE which
+defaults to *DEFAULT-DATABASE*."
+  (when (member (database-identifier name)
+                (list-sequences :owner owner :database database)
+                :test #'string-equal)
+    t))
+  
+(defun sequence-next (name &key (database *default-database*))
+  "Return the next value in the sequence NAME in DATABASE."
+  (database-sequence-next (database-identifier name) database))
+
+(defun set-sequence-position (name position &key (database *default-database*))
+  "Explicitly set the the position of the sequence NAME in DATABASE to
+POSITION."
+  (database-set-sequence-position (database-identifier name) position database))
+
+(defun sequence-last (name &key (database *default-database*))
+  "Return the last value of the sequence NAME in DATABASE."
+  (database-sequence-last (database-identifier name) database))
\ No newline at end of file