enode/enode-lisp/enode.el

1150 lines
47 KiB
EmacsLisp
Executable file

;; ENODE -- ENODE is Not an Oracle Development Environment
;; A package and 'mode' for providing an interface for examining and
;; developing for relational databases.
;; Copyright 2013 Éibhear Ó hAnluain
;; This file is part of ENODE.
;;
;; ENODE is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; ENODE 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. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with ENODE. If not, see <http://www.gnu.org/licenses/>.
;; Drawing on TOAD by Quest Software as inspiration, this is provides an emacs
;; based interface to examine and develop for a relational database. It's
;; hoped that the following databases will ultimately supported:
;; mysql
;; postgres
;; oracle
;; In fact, the proof-of-concept will be developed for an oracle database,
;; and as I will have reasonably easy access to mysql and postgres databases,
;; They will follow suit. I indend to provide high-level interfaces to
;; databases, so it will be easy to develop a layer for interacting with
;; databases from other vendors.
;;
;; Initially, the following functionality will be provided:
;; Interface -- look and feel.
;; Connect and disconnect, plus management of saved connection information.
;; Database object listing and examination.
;; Area for typing of ad hoc SQL statements.
;; Presentation of SQL query output.
;; - There'll be no facility to change data as presented from a query.
;; However, update, delete and insert commands will be facilitated
;; through the SQL area.
;;
;; Interface -- look and feel.
;; The emacs frame will be divided into three windows:
;; - The 'object list' or 'navigator' window.
;; + Here will be listed in tree format the various objects that a
;; schema has. Hierarchy will be something like: Schema, objects
;; (tables, views, packages, etc.), columns/indexes (for tables and
;; views) or procedures/functions (for packages), parameters, etc.
;; + The user will enter and leave this window by key strokes or
;; mouse clicks.
;; + An object is selected by clicking with the middle button or
;; hitting return when point is somewhere on its name. Once
;; selected, information will be presented in the information frame
;; (see below).
;; + As a tree structure will be used, opening and closing of nodes
;; will result from selection of the node as described. Selecting an
;; 'open' node will 'close' it and vice versa.
;; + A node can be closed or opened using the left and right arrow
;; keys. This will not result in selecting the node, therefore
;; preserving the information presented.
;; + This window can present any of a number of buffers. However, each
;; of these buffers must be of a perticular type (e.g. navigator),
;; and will have a local key map specific to its use. Separation of
;; the buffers will facilitate management -- one buffer for tables,
;; another for views, etc.
;; + The top node for each buffer will be the name of the schema owner.
;; + The user can toggle between showing only one schema owner in the
;; list or all of them
;; - The information window.
;; + This will present information on the item from the navigation
;; window that has most recently been selected.
;; + The user can navigate to this window by way of key stroke or
;; mouse click.
;; + The window will provide a subset of all the information on the
;; object, and a menu to facilitate showing other information.
;; + This window can present any of a number of buffers. There will be
;; a separate buffer type for each type of information being
;; displayed (i.e. table columns are presented differently from a
;; table's indexes, which is also different from the list of
;; triggers on the table, etc.)
;; - The SQL interaction window.
;; + This window will present one of two buffers -- the SQL input
;; buffer and the SQL result buffer.
;; + They can both be presented, but at the expense of the other
;; windows.
;; + In the SQL input buffer, entering commands will be as simple as
;; entering SQL commands in any sql-mode buffer.
;; + Indentation will eventually be based on my preferred indentation
;; scheme, as I am the dictator, and not necessarily benevolent.
;; + Execution of the command will involve typing a key stroke rather
;; than the RET key as we will want to format the command nicely.
;; + The output buffer will present data in one of two formats:
;; > Select commands will present the selected data in grid format.
;; > Other commands will generate output in simply sequential
;; output format.
;; - Possible arrangements can include:
;; + Three windows in two rows. The navigator and information windows
;; in the upper row, the latter being the wider. The SQL interaction
;; window being in the lower. The upper row would be the higher.
;; + Three windows in two columns. The navigator in the first column,
;; and the information and SQL interaction windows in the second.
;; The latter column will be the wider and the information window
;; will be higher than the SQL interaction window.
;; + Two windows in either columnar or tiered format. The user decides.
;; The windows will be related by function: The navigator and
;; information windows together with the latter getting more space;
;; the SQL input and output windows together, each getting equal or
;; similar space. The SQL window can be displayed in one of the
;; first two configurations if a function is called from the
;; information window that warrants it.
;; - Help information.
;; + Help can be brought up by typing the '?' key. This will present
;; the list of key strokes that perform tasks in the window which
;; has focus.
;; + The help display will be presented in the SQL interaction window,
;; which will be presented if it isn't already.
;; + If the focus is already in a buffer in the SQL interaction window,
;; the help screen will be presented in the largest visible other
;; window.
;; + Typing the '?' key in the help buffer will replace its contents
;; with all the keystrokes possible based on the type of buffer
;; supported and listing key strokes that work all over ENODE.
;; + The user can return to the buffer of most recent focus using
;; a single key stroke.
;; + The user can dismiss the help screen and replace the windows to
;; their previous configuration by typing the 'q' key while in the
;; help buffer.
;;
;;
;; Connect and disconnect.
;; - Upon startup, ENODE will ask for connection information in almost
;; precisely the manner in which sql-.+ asks for it -- using the
;; minibuffer to get the username, password and database information.
;; - ENODE will save each connection information in a history file,
;; and will maintain a completion list or lists to facilitate quick
;; connection. For connections to new databases, ENODE will ask for
;; the type of database (mysql, oracle, etc). This will be stored with
;; the connection information.
;; - The actual commands that will be executed against the database will
;; be based on the type of database being used. However, this will
;; mainly be hidden from the user.
;; - ENODE will facilitate concurrent connections.
;; - A list of possible connections can be presented in the navigation
;; screen. Open connections will be marked. Opening a closed connection
;; involved 'selecting' it. Closing an open connection should not be
;; that easy, and will involve a key stroke followed by an 'Are you
;; sure?' question. Selecting an open connection which is not the
;; current connection makes that connection current.
;; Each connection can be represented in this list either by an alias
;; given to it explicitly by the user or by a connection string in the
;; format of something like <user>/<database>@<host>
;; - Switching between connections will be at the drop of key stroke.
;; + It will be wise to figure out from the start how new connections
;; effect the buffers being displayed at the time.
;; + See above regarding switching between connections using the
;; navigator window.
;; - Closing connections can be done by one of two means:
;; + Close the current connection. Done with a key stroke and a
;; response to an 'Are you sure?' question, the next connection in
;; the list of open connections will be activated. If we are closing
;; the final connection ENODE will report this but not close the
;; application.
;; + Place the point in the connection in the navigator and execute a
;; key stroke.
;;
;; Database object listing and examination.
;; - The most useful window here will be the navigator. It will list the
;; objects of interest in a tree structure. There will be separate lists
;; for tables, views, indexes and stored procedure/functions/packages.
;; tables will drill down to triggers, columns, indexes and constraints.
;; Columns will drill down to triggers, indexes and constraints.
;; Views will drill down similarly. Packages will drill down to specs
;; and bodies. Specs will drill down to types/procedures/functions/etc.
;; Bodies will drill down to functions/procedures. Functions/procedures
;; will drill down to parameter lists and return types (where
;; appropriate).
;; - The types of information displayed and the information itself will
;; depend on the selected item, examples of which are:
;; + Tables
;; > Data
;; > Columns
;; > Constraints
;; > Indexes
;; > Triggers
;; + Views
;; > Data
;; > Columns
;; > Source
;; + Constraints
;; > Tables/Columns
;; + Packages/procedures/functions
;; > Dependancies
;; > Source
;; + Triggers
;; > Tables
;; > Source
;; In the case of views and tables, if we want to see data, it is to be
;; displayed in the SQL interaction window.
;;
;; Area for typing of ad hoc SQL statements.
;; - This will display the SQL input buffer.
;; - SQL commands can be typed as free text into the buffer.
;; - Using key strokes, certain actions can then be run on the command in
;; the buffer: execute, parse/compile, explain execution plan, etc.
;; - Depending on a prefix argument to each of the key strokes commands,
;; they will be executed on the contents of the buffer, the SQL command
;; the point is currently in or on the commands that are in the region.
;; - It will be possible to save the contents to a file.
;; - It will be possible to clear the contents in one go.
;; - It will be possible to insert the contents of a file, either after
;; point or by first clearing the buffer.
;; - Inserting the contents of the file into the buffer will not mean
;; visiting the file. That functionality will come later.
;;
;; Presentation of SQL (query) output.
;; - For commands other than select statements, the output presented will
;; be as if the commands had been run on the command line.
;; - Output from queries will be presented in a grid manner, the
;; configuration of which will be decided after some initial testing.
;;
;; Internals
;; - ENODE will maintain many lists which will be used extensively. These
;; will all be association lists. All the elements of these lists will be
;; string values, not symbols. Depending on the case sensitivity of the
;; database system, these will be case sensitive or not. The following
;; are some of these lists:
;; + Databases. This list will be populated with the first database we
;; connect to. The variable describing the current database will
;; contain a string value from this list.
;; + Schema owners. There will be a separate list of schema owners
;; per database. As we connect to databases afresh, the first two
;; elements of this list will be the user we connect as and the
;; system/root schema. The variable describing the current schema
;; owner we're connected as will contain an element from this list.
;; If the user requests to see any information pertaining to a schema
;; owner (s)he is not connected as, this list is populated fully.
;; This list can be refreshed by typing the refresh key stroke while
;; a schema owner has been selected in the navigation window.
;; Refreshing the list also refreshes its presentation in the
;; navigation window.
;; + Tables. There will be a separate list for each owner. This list
;; will be populated for the current schema owner as we connect
;; for the first time. It will be populated for other schema owners
;; as we request information on a table owned by that schema owner.
;; This list can be refreshed by typing the refresh key stroke while
;; a table is selected in the navigation window.
;; + Views. There will be a separate list for each owner. This list
;; will be populated for the current schema owner as we connect for the
;; first time. It will be populated for other schema owners as we
;; request information on a view owned by that schema owner. This list
;; can be refreshed by typing the refresh key stroke while a view is
;; selected in the navigation window.
;; + Constraints.
;; + Columns. A list per table or view.
;; + Indexes. A list per table.
;; + Packages. A list per schema owner.
;; + Procedures. A list per schema owner for non packaged procedures, a
;; list per package for packaged.
;; + Functions. A list per schema owner for non packaged functions, a
;; list per package for packaged.
;;
;; - Refreshing a list.
;; The following will happen when a command to refresh a list is called.
;; 1. An empty list will be created.
;; 2. The command to populate this new list will be executed.
;; 3. The contents of the new list will be compared with the existing
;; list and newer elements will be added to it. Elements that are
;; in the old list and are missing from the new will be removed from
;; the old.
;; 4. If the eode-refresh-recursively variable is non-nil, then
;; any sublists will also be refreshed. in this manner.
;; 5. Elements of a list that can have a sublist but that sublist
;; is nil at the time of the refresh will not have that list
;; populated. I.e. we don't refresh a list that hasn't been populated
;; yet.
;; The following will be applied during a list refresh:
;; 1. The node in the navigation list will be 'closed' before the
;; refresh begins.
;; 2. The node's parent node will be 'closed'.
;; 3. After the refresh, the parent's node will be opened again.
;; 4. If the node that had been selected at the time of the call to
;; refresh exists after the refresh, the point is sent to it and
;; it is explicitly 'selected'. If it doesn't, the node's parent
;; node is 'selected'.
;;
;; - Interacting with the database.
;; + The main engine will be the sql-<engine> functionality that is
;; provided as standard with GNU/Emacs distributions.
;; + All commands will be run in the background and will use the
;; comint-redirect-send-command* functionality.
;; + Lists will be read from temporary buffers.
;; + Presented SQL output will probably have outputting formatting
;; specified for the actual SQL interpreter being used and send to
;; the SQL output buffer.
;;
;; - Context.
;; There will be variables that will maintain:
;; + The current database and the most recent one.
;; + The current schema owner and the most recent one.
;; + The current table/view/package/etc. and the most recent one.
;; There will be a separate pair of variables for each type of
;; object ENODE supports.
;; + The current selected item. I.e. There is a table named "FOO" and
;; an index named "BAR". Both the enode-current-table,
;; enode-current-index and enode-selected-item are nil. The
;; user navigates to "FOO" in the navigation window and selects it.
;; enode-current-table and enode-selected-item are set to "FOO".
;; enode-current-index is still nil. The user then navigates to the
;; "BAR" index and selects it. enode-selected-item and
;; enode-current-index are set to "BAR", but enode-current-table
;; remains set to "FOO".
;; + The previous selected item.
;; + The current buffer and the previous one.
;; + The current window and the previous one.
;;
;; A typical session might be:
;; 1. A user calls the command M-x enode.
;; 2. The user is asked to enter a connection string, and is presented with
;; a default which is the most recently used connection.
;; 3. The user can accept the default, use M-p and M-n to scroll through
;; a list of saved connections, type in a connection (using completion
;; to assist) or type in a new connection name or type in '+'.
;; 4. If the default is accepted that connection string is used to connect.
;; If a connection from the list is entered, it's used. If a connection
;; name that isn't on the list is entered, the user wants to create a
;; new connection with that name. If '+' is entered, the user wants
;; to create a new connection but doesn't know what to call it.
;; 5. If one of the last two, the user is asked for the type of database
;; we want to connect to.
;; 6. Based on that, the sql-<vendor> function is called and the user is
;; then prompted to enter the appropriate information.
;; 7. Once connected, the windows are initialised based on the user's
;; customisation.
;; 8. The list of databases is initialised. The current database is set.
;; 9. The list of schema owners is initialised. If the user prefers to see
;; all the schema owners in the navigation window at once, this list is
;; populated with all the schema owners in the database. If not, this
;; list is initialised to the requested schema owner and the
;; system/root user. The current schema owner is set.
;; 10. The point is brought to the schema owner and the information for
;; that user is presented in the information window. enode-selected-item
;; is set.
;; If we want oracle shtuff.
(require 'enode-oracle)
;; Some handy stuff that really should be part of ENODE but isn't
(require 'sql-extras)
(require 'pls-extras)
;;
;; Customisable stuff
;;
;; The customisation group. Should be in the SQL group, but will also
;; put it into the local group.
(defgroup enode nil
"A group for customising ENODE:
The bestest database development environment ever developed for emacs."
:group 'local
:group 'SQL
:version "21.2"
)
(defcustom enode-projects-dir nil
"The location down which the SQL files for the projects will be found.
For each project, there will be a directory in this directory named for the project, a subdirectory named \"scripts/sql\" and in it will be a file named \"<project>_ENODE.sql\". This directory is used to determine the projects being used."
:type 'directory
:group 'enode
:version "21.2"
)
(defcustom enode-project-sql-file-dir nil
"The directory beneath the specific project directory, which in turn is a
subdirectory of ENODE-PROJECTS-DIR, in which the SQL \"seed\" file for the
project can be found."
:type 'directory
:group 'enode
:version "21.2"
)
;; The preferred window configuration:
(defcustom enode-initial-window-arrangement "3r"
"How do you prefer the windows to be arrayed.
Current options are:
3r: 3 windows arrayed in rows -- The navigator and the information windows
in the first row, the SQL interaction window in the second.
3c: 3 windows arrayed in columns -- The navigator in the first column, the
information and SQL interaction windows in the second.
2c: 2 windows arrayed in columns. These are the navigator and the information
windows.
2r: 2 windows arrayed in rows. These are the SQL input and SQL output windows."
:type '(choice (const :tag "Three windows, two above, one below" "3r")
(const
:tag "Three windows, one to the left and two to the right"
"3c")
(const
:tag "Two windows, navigator to the left, information to the right"
"2c")
(const
:tag "Two windows, SQL input above, SQL output below"
"2r")
)
:group 'enode
:version "21.1"
)
;; Show the schema owner alone in the navigation window, or all the
;; schema owners.
(defcustom enode-show-all-schema-owners nil
"Whether to show all the schema owners in the navigation window.
If non-nil, all the schema owners will be listed. If nil, only the current
schema owner."
:type '(choice (const :tag "Only the current schema owner" nil)
(const :tag "All schema owners" t)
)
:group 'enode
:version "21.1"
)
;; The location of the enode files.
(defcustom enode-directory "~/.enode"
"The directory where enode's configuration files will be stored"
:type 'directory
:group 'enode
:version "21.2"
)
;; The file for containing connection information
(defcustom enode-connections-file (concat enode-directory "/enode-connections")
"The file that contains the list of previous and saved connections.
The file will be an alist. Each element will be the name of the connection
and then a list of the information needed to make the connection: database
vendor, username, server, host. Password will never be stored."
:type 'file
:group 'enode
:version "21.2"
)
;; The file for containing project information
(defcustom enode-projects-file (concat enode-directory "/enode-projects")
"The file that contains the list of projects used with ENODE."
:type 'file
:group 'enode
:version "21.2"
)
;;
;; Non-customisable variables
;;
;; A simple way to change a project in a current ENODE session.
;; Not very robust.
(defalias 'enode-project 'enode)
(defvar enode-progress-message ".")
(defvar enode-selected-item nil
"The currently selected item from the navigator.
This is the item whose information is displayed in the information window.")
(defvar enode-previous-database nil
"The most recent database in use")
(defvar enode-previous-schema-owner nil
"The most recent schema owner in use")
(defvar enode-current-table nil
"The current table being examined")
(defvar enode-previous-table nil
"The table examined immediately before enode-current-table")
(defvar enode-projects nil
"The alist of projects that ENODE handles for the user")
(defvar enode-connections nil
"The alist of connections that ENODE handles for the user")
(defvar enode-current-connection-type nil
"The current connection type: 'oracle, 'mysql, etc.")
(defvar enode-supported-connection-types '(oracle)
"The current connection type: 'oracle, 'mysql, etc.")
(defvar enode-current-connection nil
"The current connection.")
(defvar enode-passwords-in-use nil
"Those passwords that have been input in this session")
(defvar enode-up nil
"Nil if ENODE is not \"running\", non-NIL if it is.")
(defvar enode-connected nil
"Nil if ENODE is not connected to a database, non-NIL if it is.")
(defvar enode-launched-as-application nil
"If ENODE has been launched as an application rather than during an otherwise
useful emacs session.")
;;
;; Some constants. Defined here and will be used throughout the code.
;;
;; The buffer that will be used to pick up the output from SQL commands.
(defconst enode-sql-command-output-buffer
(get-buffer-create " *ENODE-command-output*")
"This is the buffer that SQL output will be directed to where the information
can be picked up from.")
;;
;; Exposed functions
;;
(defun enode-automatic ()
"So that enode can be launched from the command-line. It asks for the
project in the body of the function rather than as part of a call to
INTERACTIVE"
(interactive)
(let ((the-project (completing-read "Project: "
(enode-available-projects)
nil
t))
)
(setq enode-launched-as-application t)
(enode the-project)
)
)
(defun enode (enode-project)
"Launch the enode system"
;; The name of the project we're interested in.
(interactive (list (completing-read "Project: "
(enode-available-projects)
nil
t))
)
;; We're connecting to an Oracle database
(setq enode-current-connection-type
(cadr (assoc enode-project enode-projects)))
;; Load functions for that engine
;; (load-file (format "enode-%S.el" enode-current-connection-type))
;; Let's call
(enode-start-engine)
(other-window 1)
;; The project's "general purpose" SQL file.
(let ((sql-file-name (format "%s/%s/%s/%s_ENODE.sql"
enode-projects-dir
enode-project
enode-project-sql-file-dir
enode-project)))
(find-file sql-file-name)
)
(setq enode-up t)
;; The SQL engine is called without connection information. Log in now.
(call-interactively 'enode-connection)
)
(defun enode-start-engine()
"A function to start the SQL engine for the connection type"
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-start-sql-engine))
)
)
(defun enode-stop-engine()
"A function to stop the SQL engine for the connection type"
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-stop-sql-engine))
)
)
(defun enode-connect (connection-description)
"A function to initiate a connection based on connection type"
(let ((conn-details (cdr (assoc connection-description
(enode-get-connections))))
;; Somewhere to come back to.
(active-buffer (current-buffer))
)
;; The user to connect as
(setq conn-user (car conn-details))
;; The database to connect to
(setq conn-connection (caddr conn-details))
;; The password to use: if it's not in the saved connection
;; details...
(setq conn-pass (if (not (cadr conn-details))
;; ... if it's one of the locally saved
;; passwords...
(if (assoc connection-description
enode-passwords-in-use)
;; ... use it.
(car (cdr (assoc connection-description
enode-passwords-in-use)))
;; otherwise...
;; Read the password from the user
(let ((my-conn-pass
(read-passwd
(format "Password for %s on %s: "
conn-user
conn-connection))))
;; add it to the list of those in use.
(setq enode-passwords-in-use
(cons (list connection-description
my-conn-pass)
enode-passwords-in-use))
my-conn-pass))))
;; The SQL prompt for the SQL engine
(setq conn-prompt (cadddr conn-details))
(cond ((eq 'oracle enode-current-connection-type)
;; Connect.
(enode-oracle-connect conn-user conn-pass conn-connection
conn-prompt)
)
)
;; Say who we're now connected to.
(setq enode-current-connection connection-description)
(setq enode-connected t)
(set-buffer active-buffer)
)
)
(defun enode-disconnect ()
"A function to close a connection to the database"
(interactive)
;; Somewhere to come back to.
(let ((active-buffer (current-buffer)))
(cond ((eq 'oracle enode-current-connection-type)
;; Disconnect.
(enode-oracle-disconnect)
)
)
;; Say who we're now connected to.
(setq enode-current-connection nil)
(setq enode-connected nil)
(set-buffer active-buffer)
)
)
(defun enode-query-connection ()
"A function to tell you who you are and what system you're connected to"
(interactive)
;; Print out the information
(message (cond ((not enode-up) "ENODE not running")
((not enode-connected) "ENODE not connected")
(t enode-current-connection))
)
)
(defun enode-connection (connection-description)
"A command to connect to another user"
;; Get a connection description from the minibuffer
(interactive (list (completing-read "Connection: "
(enode-get-connections)
))
)
;; If the passed description is not a saved description...
(if (not (assoc connection-description (enode-get-connections)))
;; ... get the relevant information and save it.
(enode-add-new-connection connection-description)
)
;; Details for this connection from the list of available connections
(enode-connect connection-description)
)
(defun enode-clear-stored-password (username)
"A function to clear a password from a list of passwords"
; Whose password we want to clear.
(interactive (list (completing-read "User: "
enode-passwords-in-use
))
)
;; Create two temporary lists...
;; ... the first based on the current password list
(let ((tmp-password-list enode-passwords-in-use)
;; the second a blank list to build a new list.
tmp-password-list-2)
;; For as long as the first list is not empty...
(while tmp-password-list
;; ... if the first entry is not the username we're interested in...
(if (not (string= (caar tmp-password-list) username))
;; ... add the first entry to the new list.
(setq tmp-password-list-2 (cons (car tmp-password-list)
tmp-password-list-2))
)
;; Remove the first entry from the temporary list
(setq tmp-password-list (cdr tmp-password-list)))
;; Reset the password list with the password information of interest
;; removed.
(setq enode-passwords-in-use tmp-password-list-2)
)
)
(defun enode-list-users ()
"A function to throw up a list of the database users for the current
connection."
(interactive)
;; A buffer to send the information to
(let ((user-list-buffer (enode-buffer "*ENODE-users*")))
;; How are we connected?
;; Oracle database...
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-list-users user-list-buffer)
)
)
;; Go to the buffer with the information
(display-buffer user-list-buffer)
;; We're done.
(message "Done.")
)
)
(defun enode-desc (object-name)
"A function to describe an object."
(interactive (list (read-from-minibuffer "Describe: " )))
(let ((desc-buffer
(enode-buffer (format "*ENODE-describe-%s*" object-name)))
)
;; How are we connected?
;; Oracle database...
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-desc object-name desc-buffer)
)
)
(display-buffer desc-buffer)
(message "Done.")
)
)
(defun enode-list-invalid-objects (dbuser &optional object-type)
""
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser object-type t)
)
(defun enode-list-objects (dbuser &optional object-type invalid-flag
filter-string)
"A function to throw up a list of the database objects owned by the specified
DBUSER.
When called interactively, the current user is used."
;; Who we're currently connected as
(interactive (list (upcase (enode-connected-user))))
;; The buffer to send the output to.
(let ((object-list-buffer
(enode-buffer
(format
"*ENODE-%s-%s*" dbuser
(if object-type (format "%ss" (downcase object-type)) "objects")
))
)
)
;; How are we connected?
;; Oracle database...
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-list-objects object-list-buffer dbuser object-type
invalid-flag filter-string)
)
)
;; Go to the buffer (again?)
(display-buffer object-list-buffer)
;; We're Done.
(message "Done.")
)
)
(defun enode-list-object-types (&optional dbuser)
"A function to throw up a list of the object types."
;; Who we're currently connected as
(interactive)
;; The buffer to send the output to.
(let ((object-type-list-buffer (enode-buffer "*ENODE-object-types*")))
;; How are we connected?
;; Oracle database...
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-list-object-types object-type-list-buffer dbuser)
)
)
;; Show the buffer
(display-buffer object-type-list-buffer)
;; We're Done.
(message "Done.")
)
)
(defun enode-list-tables (dbuser)
"A function to throw up a list of the database user's tables"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "TABLE")
)
(defun enode-list-tables-filtered (dbuser filter-string)
"A function to throw up a list of the database user's tables"
(interactive (list (upcase (enode-connected-user))
(upcase (read-from-minibuffer
"Case-insensitive name substring: ")
)
)
)
(enode-list-objects dbuser "TABLE" nil filter-string)
)
(defun enode-list-views (dbuser)
"A function to throw up a list of the database user's views"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "VIEW")
)
(defun enode-list-materialised-views (dbuser)
"A function to throw up a list of the database user's views"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "MATERIALIZED VIEW")
)
(defun enode-list-functions (dbuser)
"A function to throw up a list of the database user's functions"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "FUNCTION")
)
(defun enode-list-procedures (dbuser)
"A function to throw up a list of the database user's procedures"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "PROCEDURE")
)
(defun enode-list-packages (dbuser)
"A function to throw up a list of the database user's packages"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "PACKAGE")
)
(defun enode-list-package-bodies (dbuser)
"A function to throw up a list of the database user's package bodies"
(interactive (list (upcase (enode-connected-user))))
(enode-list-objects dbuser "PACKAGE BODY")
)
;;
;; Internal functions
;;
(defun enode-project-available (enode-project)
"A function to check whether a project is available. It returns TRUE if
the project's top directory is there AND if the enode file is present."
(and (file-exists-p (format "%s/%s/%s/%s_ENODE.sql"
enode-projects-dir
(car enode-project)
enode-project-sql-file-dir
(car enode-project))))
)
(defun enode-available-projects ()
"A function to take the list of ENODE projects and to turn it into a list
of projects that are available."
(let ((the-project-list (enode-get-projects))
(available-project-list ()))
(while the-project-list
(if (enode-project-available (car the-project-list))
(setq available-project-list
(append available-project-list (list (car the-project-list))))
)
(setq the-project-list (cdr the-project-list))
)
available-project-list
)
)
(defun enode-connected-user ()
"A function to return the username of the currently connected user"
;; Get the current connection description
(let ((my-conn (enode-query-connection)))
;; Locate the username part of it
(string-match "^\\(.+\\)@" my-conn)
;; Return that part
(substring my-conn (match-beginning 1) (match-end 1))
)
)
(defun enode-add-new-connection (conn-desc)
"A function to add a new connection to the list of current connections"
;; Get the list of all the possible connections
(let ((all-connections (enode-load-connections-list))
;; Get the list of the connections of the current type.
(current-type-connections (enode-get-connections)))
;; As long as the description we want to create a connection for doesn't
;; exist already...
(if (not (assoc conn-desc current-type-connections))
(progn
;; Get the username
(setq conn-user
(read-from-minibuffer (format "Username for %s: " conn-desc)))
;; Get the connection name
(setq conn-connection
(read-from-minibuffer (format "Database for %s: " conn-desc)))
;; Get the prompt that will be used
(setq conn-prompt
(read-from-minibuffer (format "SQLPROMPT for %s: " conn-desc)))
;; Add these details to the list of connections for the current type
(setq current-type-connections
(cons (list conn-desc
conn-user
nil
conn-connection
conn-prompt)
current-type-connections))
;; Replace the connections for this type in the list of all
;; connections with the new list for this type.
(setq all-connections (cons (cons enode-current-connection-type
current-type-connections)
(assq-delete-all
enode-current-connection-type
all-connections)))
;; Open the file that contains the connections
(find-file enode-connections-file)
;; Go to the start of the file.
(goto-char (point-min))
;; Look for where the information is specified
(re-search-forward "^(setq enode-connections")
;; Remove that command.
(beginning-of-line)
(kill-sexp)
;; (delete-region (point) (save-excursion (end-of-sexp)(point)))
;; Insert a replacement command
(insert (format "(setq enode-connections '%S)" all-connections))
;; Write the file and dismiss the buffer
(save-buffer)
(kill-buffer (current-buffer))
;; Load the new connection information
(enode-load-connections-list)
)
)
)
)
(defun enode-get-projects ()
"A function to build a list of projects"
;; If the variable is set already, return it
(if (not enode-projects)
(load-file enode-projects-file))
enode-projects
)
;; Get the list of connections for the current type of connection
(defun enode-get-connections ()
"A function to return the list of connections for the current ENODE connection type (as specified in ENODE-CURRENT-CONNECTION-TYPE)."
(if (not enode-connections)
(enode-load-connections-list))
(cdr (assoc enode-current-connection-type enode-connections))
)
;; Load up the list of connections.
(defun enode-load-connections-list ()
"Load the connection information from the file."
(load-file enode-connections-file)
enode-connections
)
(defun enode-buffer (buffer-name)
"A generic function for creating and returning a buffer."
;; Get the buffer
(let ((the-buffer (get-buffer-create buffer-name)))
;; Empty it
(enode-clean-buffer the-buffer)
;; Return it
the-buffer
)
)
(defun enode-wait-for-command ()
"A function to wait for a comint redirect command to complete"
(save-excursion
(set-buffer sql-buffer)
(while (null comint-redirect-completed)
(message enode-progress-message)
(setq enode-progress-message
(concat enode-progress-message "."))
(accept-process-output nil 0 1000))))
(defun enode-clean-buffer (enode-buffer)
"A function to empty the ENODE-BUFFER."
(save-excursion
(set-buffer enode-buffer)
(erase-buffer)
)
)
(defun enode-code-buffer (code-type &optional code-name code-owner)
"A generic function for creating and returning a buffer for a piece of code."
;; Get the buffer
(let ((the-buffer
(get-buffer-create
(format "*enode-%S-%s%s*"
enode-current-connection-type
(downcase code-type)
(if code-name
(format "-%s%s"
(if code-owner (format "%s." code-owner) "")
code-name)
"")
)
)
)
)
;; Empty it
(enode-clean-buffer the-buffer)
;; Return it
the-buffer
)
)
(defun enode-new-project (proj-name)
"A function to set up a new project"
(interactive "sProject name: ")
;; Raise an error if the project already exists.
(if (assoc proj-name enode-projects)
(error "Project already exists")
)
;; Determine the directory for this new project.
(let ((proj-dir (format "%s/%s" enode-projects-dir proj-name)))
;; If the base SQL directory for the project doesn't exist, create it.
(if (not (file-exists-p (format "%s/%s" proj-dir
enode-project-sql-file-dir)))
(make-directory (format "%s/%s" proj-dir
enode-project-sql-file-dir) t))
;; Get the base SQL file for the project.
(find-file (format "%s/%s/%s_ENODE.sql" proj-dir
enode-project-sql-file-dir proj-name))
)
;; Add the new project to the list of maintained projects.
(enode-add-project-to-list proj-name)
)
(defun enode-add-project-to-list (proj-name)
"A function to add a project to the list of usable projects"
;; If it isn't already set, determine the connection type for this
;; new project.
(if (not enode-current-connection-type)
(setq enode-current-connection-type
(make-symbol
(read-from-minibuffer
(format
"Please enter a connection type (supported options: %S): "
enode-supported-connection-types
))))
)
;; Add the project to the active list
(setq enode-projects (cons (list proj-name enode-current-connection-type)
enode-projects))
(save-excursion
;; Get the file
(find-file enode-projects-file)
;; Look for where the information is specified
(re-search-forward "^(setq enode-projects")
;; Remove that command.
(beginning-of-line)
(kill-sexp)
;; (delete-region (point) (save-excursion (end-of-sexp) (point)))
;; Insert a replacement command
(insert (format "(setq enode-projects '%S)" enode-projects))
;; Write the file and dismiss the buffer
(save-buffer)
(kill-buffer (current-buffer))
)
)
(defun enode-rollback ()
"A command to rollback a change."
(interactive)
(enode-commit t)
)
(defun enode-commit (&optional rollback)
"A command to commit a change to a database"
(interactive)
;; How are we connected?
;; Oracle database...
(cond ((eq 'oracle enode-current-connection-type)
(enode-oracle-commit rollback)
)
)
)
(defun enode-quit ()
"A command to exit ENODE"
(interactive)
(let ((my-buffer-list (mapcar 'buffer-name (buffer-list)))
enode-buffer-list
(my-case-fold-search case-fold-search)
user-reply
)
(setq case-fold-search t)
(while my-buffer-list
(if (or (string-match "enode" (car my-buffer-list))
(buffer-file-name (get-buffer (car my-buffer-list))))
(setq enode-buffer-list
(cons (car my-buffer-list) enode-buffer-list))
)
(setq my-buffer-list (cdr my-buffer-list))
)
(with-temp-buffer
(insert (format "\n\nQuitting ENODE!\n\n"))
(insert (format "%s%s"
"You are about to leave ENODE. This will close the SQL "
"session and then kill the following buffers:\n\n"))
(let ((my-enode-buffer-list enode-buffer-list))
(while my-enode-buffer-list
(insert (format " %s %s\n"
(if (buffer-modified-p
(get-buffer
(car my-enode-buffer-list)))
"*"
"%")
(car my-enode-buffer-list))
)
(setq my-enode-buffer-list (cdr my-enode-buffer-list))
)
)
(insert (format "\n"))
(insert (format "%s%s"
"If you want to keep any of these, say \"no\" now and "
"rename them."))
(display-buffer (current-buffer))
(setq user-reply (yes-or-no-p "Are you sure you want to quit ENODE? "))
)
(if user-reply
(progn
(enode-stop-engine)
(kill-buffer sql-buffer)
(while enode-buffer-list
(kill-buffer (car enode-buffer-list))
(setq enode-buffer-list (cdr enode-buffer-list))
)
(setq enode-up nil)
(if enode-launched-as-application
(save-buffers-kill-emacs)
)
)
)
)
)
(provide 'enode)